/**
 * @fileOverview
 * WashizuMahjong.
 * http://code.google.com/p/washizu-mahjong/
 */
//(function(){

//set prototype function
Array.prototype.clone=function(){function d(a){if(null==a)return null;var c=new a.constructor,b;for(b in a)c[b]="object"==typeof a[b]?d(a[b]):a[b];return c}var c=[],a;for(a=0;a<this.length;a++)c[a]="object"==typeof this[a]?d(this[a]):this[a];return c};
Array.prototype.remove=function(a){a=this.indexOf(a);return 0<=a?(this.splice(a,1),!0):!1};
Array.prototype.removeByIndex=function(a){if("number"!=typeof(a+0))return!1;return 0<=a?(this.splice(a,1),!0):!1};
Array.prototype.addAt=function(a,b){if("number"!=typeof a||this.length<a)return!1;this.splice(a,0,b);return!0};
Array.prototype.swap=function(a,b){if("number"!=typeof a||this.length<a||"number"!=typeof b||this.length<b||a==b)return!1;var c=this[a];this.splice(a,1,this[b]);this.splice(b,1,c);return!0};
Array.prototype.lastItem=function(){return this[this.length-1]};

/**
 * All tiles in the game.
 * it store all datas of the tiles, therefore when it need to start new game, just need to clone this to reset the data of the tiles.
 * @type MahjongTile[]
 * @constant
 */
var DEFAULT_TILES =[],
/**
 * The tiles in the game. Items in the array will be removed due to the game. Also, when {@link startNewRound} function run, it will also clone the DEFAULT_TILES.
 * @see DEFAULT_TILES
 * @see Game-startNewRound
 * @see Array#clone
 * @type MahjongTile[]
 */
gameTiles = [],
/**
 * @type Player[]
 */
players = [],
/**
 * the hidden tile, only used for display hidden tiles, such as hidden <span class="unicode">&#126976;</span>
 * @type MahjongTile
 * @nullable true
 */
hiddenTile = null;

/**
 * @namespace
 * The round detail
 */
var RoundDetail = {
	/**
	 * The number of the round. If it is 東二局, its value is 2.
	 * @type Number
	 */
	number: 1,
	/**
	 * The code of the round. The value will be 東(East): 0; 南(South): 1; 西(West): 2; 北(North): 3.
	 * @type Number
	 * @default 0
	 */
	code: 0,
	/**
	 * @type String
	 * @see RoundDetail#code
	 */
	get round_char(){
		switch(this.code){
			case 0:
				return "\u6771";
			case 1:
				return "\u5357";
			case 2:
				return "\u897f";
			case 3:
				return "\u5317";
			default:
				return "\u6771";
		}
	},
	/**
	 * The round name.
	 * @type String
	 */
	get round_name(){
		var getChineseNum = function(code){
			function c(z){switch(z){case 1:return"\u4e00";case 2:return"\u4e8c";case 3:return"\u4e09";case 4:return"\u56db";case 5:return"\u4e94";case 6:return"\u516d";case 7:return"\u4e03";case 8:return"\u516b";case 9:return"\u4e5d"; default: return "";}};
			if(code < 10)
				return c(code);
			else if(code < 20)
				return "十" + c(code % 10)

		}
		if(this.counterSticks == 0){
			return this.round_char + getChineseNum(this.number) + "\u5c40";
		}else{
			return this.round_char + getChineseNum(this.number) + "\u5c40\t" + getChineseNum(this.counterSticks) + "\u672c\u5834";
		}
	},
	/**
	 * 場棒
	 * @type Number
	 */
	counterSticks: 0,
	/**
	 * <ruby>親<rp>（</rp><rt>おや</rt><rp>）</rp></ruby>.
	 * The dealer of the round.
	 * @see Game.startNewGame
	 * @see Game.startNewRound
	 */
	dealer: 0,
	/**
	 * The number of how many tiles are picked up
	 * @type Number
	 * @see MAX_DISCARD_TILES
	 */
	pickedUpTilesNum: 0,
	/**
	 * For each round, the total number of the times of kong is 4. This number is the numbers of kong in a round
	 * @type Number
	 * @default 0
	 */
	kongTimes: 0,
	/**
	 * @type MahjongTile[]
	 */
	discardTiles: []
};

/**
 * @namespace
 * Mahjong Type
 * @see MahjongTile#tileType
 * @see MahjongTile#typeCode
 */
var MahjongType = {
	/**
	 * 餅
	 * @type Number
	 */
	Pin: 0,
	/**
	 * 萬
	 * @type Number
	 */
	Wan: 1,
	/**
	 * 索
	 * @type Number
	 */
	Sou: 2,
	/**
	 * 字牌
	 * @type Number
	 */
	Word: 3
}

/**
 * @namespace
 * The num code of Mahjong Word Tiles
 * @see Mahjong#wordNum
 */
var MahjongWordNumCode = {
	/**
	 * 東
	 * @type Number
	 */
	East: 0,
	/**
	 * 南
	 * @type Number
	 */
	South: 1,
	/**
	 * 西
	 * @type Number
	 */
	West: 2,
	/**
	 * 北
	 * @type Number
	 */
	North: 3,
	/**
	 * 白
	 * @type Number
	 */
	Haku: 4,
	/**
	 * 發
	 * @type Number
	 */
	Hatsu: 5,
	/**
	 * 中
	 * @type Number
	 */
	Chun: 6
}


/**
 * @namespace
 * @see Meld
 */
var MeldType = {
	/**
	 * 槓
	 * @type String
	 */
	KONG: "KONG",
	/**
	 * 碰
	 * @type String
	 */
	PONG: "PONG",
	/**
	 * 吃
	 * @type String
	 */
	CHOW: "CHOW",
	/**
	 * @type String
	 */
	NONE: "NONE"
}

/**
 * @namespace
 * @see Player#canKong
 * @see Player#kongAction
 */
var KongType = {
	/**
	 * 暗槓
	 * The closed quad whose kong tile is in the hand
	 * @type Number
	 * @readonly
	 */
	Closed_Quad_from_Hand: 1,
	/**
	 * 暗槓.
	 * The closed quad whose kong tile is the picking up tile.
	 * @type Number
	 * @readonly
	 */
	Closed_Quad_from_PickupTile: 2,
	/**
	 * 大明槓
	 * @type Number
	 * @readonly
	 */
	Open_Quad: 3,
	/**
	 * 加槓
	 * @type Number
	 * @readonly
	 */
	Added_Open_Quad: 4,
	/**
	 * @type Number
	 * @readonly
	 */
	None: 0
}
 
/**
 * Since sometimes players use different rule to play the game. Therefore use this object to set the rule.
 * @namespace
 */
var Rules = {
	/**
	 * @description 赤ドラ. <br />
	 * allow Red Dora
	 * @type Boolean
	 */
	Red_Dora : false,
	/**
	 * @description The simple number of Red Doras. It may be 3, 5, or 7.
	 *
	 * @type Number
	 */
	Red_Dora_Num: 5,
	/**
	 * @description ダブル役満.<br />
	 * Double Yakuman
	 * @type Boolean
	 */
	Double_Yakuman : true,
	/**
	 * @description <ruby>青天井ルール<rp>（</rp><rt>あおてんじょうルール</rt><rp>）</rp></ruby>.<br />
	 * In this rule, the socore winner get does not have the maxium number.
	 * @type Boolean
	 */
	Aotenjou : false,
	/**
	 * @type Number
	 */
	Score_Per_Sticks: 100,
	/**
	 * @see Game.AbortiveDraws
	 * @type Boolean
	 */
	Multiple_Agari: true,
	/**
	 * @see Game.AbortiveDraws.tripleRon
	 * @type Boolean
	 */
	Triple_Ron: false,
	/**
	 * @see Game.AbortiveDraws
	 * @type Boolean
	 */
	Four_Richies: false
};


/**
 * @namespace
 */
var Special_Fu_Value = {
	/**
	 * 国士無双
	 * @type Number
	 */
	Thirteen_Orphans: 30,
	/**
	 * 七対子
	 * @type Number
	 */
	Seven_Pairs: 25
};

/**
 * @class
 * @param {String}	actionType
 * @param {Player}	[player]
 * @param {MahjongTile}	[tile]
 * @param {Player}	[actedplayer]
 * @param {Object}	[otherInfo]
 */
function ActionMessage(actionType, player, tile, actedplayer, otherInfo){
	if(!(this instanceof ActionMessage))
		return new ActionMessage(actionType, player, tile, actedplayer, otherInfo);
	if(!actionType){
		return;
	}
	this.type = actionType;
	if(player && (player instanceof Player)){
		this.player = player;
	}
	if(tile && (tile instanceof MahjongTile)){
		this.tile = tile;
	}
	this.actedPlayer = actedplayer || null;
	this.otherInfo = otherInfo || null;
	if(this.callback && typeof(this.callback) == "function")
		this.callback();
}
/**
 * ActionMessage type.
 * @see	ActionMessage.AddDora
 * @see	ActionMessage.AddTile
 * @see	ActionMessage.Chow
 * @see	ActionMessage.Discard
 * @see	ActionMessage.Kong
 * @see	ActionMessage.NewRound
 * @see	ActionMessage.PayScore
 * @see	ActionMessage.PickUp
 * @see	ActionMessage.Pong
 * @see	ActionMessage.ReciveScore
 * @see	ActionMessage.Richi
 * @see	ActionMessage.AbortiveDraw
 * @see	ActionMessage.DrawRound
 * @see	ActionMessage.SelfDrawn
 * @see	ActionMessage.Ron
 * @type String
 */
ActionMessage.prototype.type = null;
/**
 * The player did the action.
 * @type Player
 */
ActionMessage.prototype.player = null;
/**
 * The player be acted. For example, the player discard the tile when the pong action happen.
 * @type Player
 * @default null
 */
ActionMessage.prototype.actedPlayer = null;
/**
 * @type MahjongTile
 */
ActionMessage.prototype.tile = null;
/**
 * Other informations about the action.
 * @type Object
 */
ActionMessage.prototype.otherInfo = {};
/**
 * The callback function, it can be used for storing game data.
 * @type function
 */
ActionMessage.prototype.callback = function(){
	console.log(this.player|| "", this.type, this.tile, this.actedPlayer || "", this.otherInfo || "",new Date());
};
/**
 * @type String
 */
ActionMessage.PickUp = "Pick Up";
/**
 * @type String
 */
ActionMessage.AddTile = "Add Tile into Hand";
/**
 * @type String
 */
ActionMessage.Kong = "Kong";
/**
 * @type String
 */
ActionMessage.Chow = "Chow";
/**
 * @type String
 */
ActionMessage.Pong = "Pong";
/**
 * @type String
 */
ActionMessage.Discard = "Discard";
/**
 * @type String
 */
ActionMessage.AddDora = "AddDora";
/**
 * @type String
 */
ActionMessage.Richi = "Richi";
/**
 * @type String
 */
ActionMessage.ReciveScore = "Recive Score";
/**
 * @type String
 */
ActionMessage.PayScore = "Pay Score";
/**
 * @type String
 */
ActionMessage.NewRound = "New Round";
/**
 * @type String
 */
ActionMessage.AbortiveDraw = "Abortive Draw";
/**
 * @type String
 */
ActionMessage.DrawRound = "Draw Round";
/**
 * @type String
 */
ActionMessage.Ron = "Picking Up a Discard";
/**
 * @type String
 */
ActionMessage.SelfDrawn = "Self-Drawn";
/**
 * Sending the detail of an action, such as kong, chow... etc.
 * @see Player#AI
 * @see Player#
 * @namespace
 */
var DataTransfer = new function(){
	/**
	 * @type Worker|WebSocket
	 */
	var _interface = {
		postMessage: function(){},
		send: function(){},
		addEventListener: function(){}
	};
	/**
	 * @see DataTransfer-reciveMessage
	 */
	function defaultMessageHandler(_message){
		
	}
	this.__defineSetter__("interface", function(e){
		if(e instanceof Worker || e instanceof WebSocket){
			_interface = e
			this.reciveMessage(defaultMessageHandler)
		}
	});
	
	/**
	 * @param {DataTransfer.data} data The information sent to the interface
	 */
	this.sendMessage =  function(data){
		if(_interface instanceof Worker){
			_interface.postMessage(data);
		}else if(_interface instanceof WebSocket){
			_interface.send(data)
		}
	};
	
	this.reciveMessage = function(handler){
		if(typeof handler != "function"){ 
			return false
		}
		_interface.addEventListener("message", handler);
	};
	/**
	 * @constructor
	 * @param {Player} player
	 * @param {String} action The same as {@link ActionMessage#type}
	 * @param {MahjongTile[]} chowList When the player chow, the player also need to send the detail of their "chow list". See {@link Player#chowList}.
	 */
	this.data = function(player, action, chowList){
		/**
		 * @type Player
		 */
		this.player = player;
		/**
		 * The action of the message. It should be {@link ActionMessage.AbortiveDraw}, {@link ActionMessage.DrawRound}, {@link ActionMessage.Ron}.
		 * @see ActionMessage#type
		 */
		this.action = action;
		/**
		 * @see RoundDetail
		 */
		this.gameDetails = RoundDetail;
		/**
		 * @see Rules
		 */
		this.rule = Rules;
		/**
		 * @type MahjongTile
		 */
		this.dicardTile = players[Game.turn].discardTiles.lastItem();
		/**
		 * @type Number
		 */
		this.numOfPlayers = players.length
	};
}


/**
 * @namespace
 */
var Dora = new function(){
	/**
	 * To add dora. When a round start, it will has 1 dora. Also, when a player doing kong, it also add new dora.
	 * @see Dora.doraIndicators
	 * @see Dora.doraIndicators
	 * @see Game.startNewRound
	 * @see Player#kongAction
	 */
	this.addDora = function(){
		// set dora
		var index = parseInt(Math.random() * gameTiles.length);
		this.doraIndicators.push(gameTiles[index]);
		ActionMessage(ActionMessage.AddDora, null, gameTiles[index], null, {doratype: "normal Dora"});
		gameTiles.removeByIndex(index);

		// set ura dora
		index = parseInt(Math.random() * gameTiles.length);
		this.uraDoraIndicators.push(gameTiles[index]);
		ActionMessage(ActionMessage.AddDora, null, gameTiles[index], null, {doratype: "Ura Dora"});
		gameTiles.removeByIndex(index);
	}
	/**
	 * 表ドラ／槓ドラ表示牌
	 * @type MahjongTile[]
	 */
	this.doraIndicators = [];
	/**
	 * 裏ドラ／槓ウラ表示牌
	 * @type MahjongTile[]
	 */
	this.uraDoraIndicators = [];
	/**
	 * Clear doras.
	 * @see Game.startNewRound
	 */
	this.clean = function(){
		this.doraIndicators = [];
		this.uraDoraIndicators = [];
	}
	/**
	 * Since the rule of dora is that if dora tile is <span class="unicode">&#126976;</span>, dora is<span class="unicode">&#126977;</span>.
	 * This function is used to get real dora list.
	 * @param {MahjongTile[]}[d=dora] the dora list.
	 * @see Dora.doraIndicators
	 * @see Dora.doraIndicators
	 * @return {MahjongTile[]}	The doras. For example, if dora indicators is 一萬（🀇）, it will return 二萬（🀈）.
	 */
	this.getDoras = function(d){
		d = d || this.doraIndicators;

		var list = [];
		var t="\u6771,\u5357,\u897f,\u5317,\u767d,\u767c,\u4e2d".split(","),
		    chinesetext="\u4e00,\u4e8c,\u4e09,\u56db,\u4e94,\u516d,\u4e03,\u516b,\u4e5d".split(","),
		    typeList=["\u5b57\u724c","\u842c","\u7d22","\u9905"];
		for(var i = 0; i < d.length; i++){
			var nText = d[i].tileName;	//new tile name
			var nType = d[i].tileType;	//new tile type
			var nPicText = d[i].tilePicText;
			var smallestPic;

			if(nType == typeList[0]){
				//字牌
				var tIndex = t.indexOf(nText);
				if(tIndex < 4){
					//東南西北
					smallestPic = 126976;
					nText = t[(tIndex + 1) % 4];
					nPicText = "&#"+(parseInt(smallestPic)+((tIndex + 1) % 4))+";";
				}else if(tIndex < 7){
					//白發中
					smallestPic = 126980;
					nText = t[(tIndex+1-4)%3+4];
					picInt = parseInt(nPicText.replace("&#","").replace(";",""));
					nPicText = "&#" + ((picInt-1-smallestPic+3)%3+smallestPic) + ";";
				}
			}else{
				//萬	索	餅
				var nNum=nText[0], newIndex = (chinesetext.indexOf(nNum)+1)%chinesetext.length;
				nText = chinesetext[ newIndex ] + nText[1];
				differenceBetweenOldAndNewIndex = newIndex - chinesetext.indexOf(nNum);

				nPicText = "&#" + (parseInt(nPicText.replace("&#","").replace(";",""))+differenceBetweenOldAndNewIndex) + ";";
			}
			list.push(new MahjongTile(nText, nType, nPicText ));
		}
		return list;
	}
}

/**
 * @namespace
 */
var Game = new function(){
	/**
	 * To set the turn of the players.
	 * @type Number
	 * @see Player#chowAction
	 * @see Player#kongAction
	 * @see Player#pickUp
	 * @see Player#pongAction
	 * @see Player#toDiscardTile
	 */
	this.turn = 0;
	/**
	 * @type Boolean
	 * @see Game.countScore
	 * @see Game.drawRound
	 * @see Game.finishRound
	 */
	var isRoundFinish = false;
	/**
	 * Whether the dealer win in the round.
	 * @type Boolean
	 */
	var isDealerWin = false;
	/**
	 * Whether the round is finished by abortive draw.
	 * @type Boolean
	 * @see Game.AbortiveDraws
	 */
	var isAbortiveDraw = false;
	/**
	 * @type Boolean
	 * @see Game.AbortiveDraws.nineYaokyuTiles
	 */
	var isDealerMakeAbortiveDraw = false;
	/**
	 * When the game support mutiple-ron and one player is ronned by mutiple players, this array will save the details of all players winned.
	 * @type Player[]
	 * @see Game.countScore
	 */
	var mutiRonPlayer = [];
	/**
	 * @type Number[]
	 * @see Game-mutiRonPlayer
	 * @see Game.countScore
	 */
	var scorePositionOfStart = [];
	/**
	 * The object of Round Detail.
	 * If it is tripple ron, the new round should be change as a draw round.
	 * This object is used for saving the detail of the round.
	 * @see Rules.Triple_Ron
	 * @see RoundDetail.number
	 * @see RoundDetail.code
	 * @see RoundDetail.counterSticks
	 * @see RoundDetail.dealer
	 * @see Game.AbortiveDraws.tripleRon
	 */
	var _roundDetail = {
		number: null,
		code: null,
		counterSticks: null,
		dealer: null
	};
	this.__defineGetter__("isRoundFinish", function(){
		if(Rules.Multiple_Agari && mutiRonPlayer.length > 0){
			return true
		}
		return isRoundFinish
	});
	/**
	 * The max numbers of discarded tiles
	 * @constant
	 * @type Number
	 * @see RoundDetail.pickedUpTilesNum
	 */
	this.__defineGetter__("MaxPickedTilesNum", function(){
		return DEFAULT_TILES.length - 13 * players.length - 14
	});	
	/**
	 * @param {Mahjong[]} $$ The same as DEFAULT_TILES.
	 * @see Game.startNewGame
	 * @see DEFAULT_TILES
	 */
	function setTiles($$){
		//$$ : default tiles; $$$ : the tiles used in game
		if(!(typeof($$)=="object" && ($$ instanceof Array)))return;
		var t="\u6771\u5357\u897f\u5317\u4e2d\u767c\u767d\u842c\u7d22\u9905".split(""),
		    chinesetext="\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d".split(""),
		    typeList=["\u5b57\u724c","\u842c","\u7d22","\u9905"];
		try{
			//make hiddenTile
			hiddenTile = new MahjongTile(" ", " ", "&#127019;", 1, Image_Foleders_of_Mahjong_Tiles.Back_Tile_Image);
		}catch(e){}

		for(var i = 126976; i <= 127009; i++){//0xD83C 0xDC00 -> 0xD83C 0xDC21 + 0xD83C 0xDC2B
			var mahjongListIndex = i - 126976;
			var n,type;
			var tileImgUrl;
			if(mahjongListIndex < 10)	{tileImgUrl="Tile00"+mahjongListIndex+".png";}
			else				{tileImgUrl="Tile0"+parseInt(mahjongListIndex)+".png";}

			if(mahjongListIndex < 7){
				//字牌
				type =  typeList[0];
				n = t[mahjongListIndex];
				for(var f = 0; f < 4 ; f++){
					var _m = new MahjongTile(n, type, "&#" + i + ";", (f == 0), tileImgUrl);
					_m.id = $$.length;
					$$.push(_m);
				}
			}else{
				type = typeList[parseInt((mahjongListIndex - 7) / 9)+1];
				n = chinesetext[parseInt((mahjongListIndex - 7) % 9)] + type;
				for(var f = 0; f < 4 ; f++){
					var canRedDora = (f%2 == 0 && parseInt((mahjongListIndex - 7) % 9) + 1 == Rules.Red_Dora_Num && Rules.Red_Dora);
					var _m = new MahjongTile(n, type, "&#" + i + ";", (f == 0), tileImgUrl, canRedDora);
					_m.id = $$.length;
					$$.push(_m);
				}
			}

		}
	}

	/**
	 * To start a new round.
	 */
	function startNewRound(){
		ActionMessage(ActionMessage.NewRound, null, null, null, JSON.parse(JSON.stringify(RoundDetail)));
		//reset tiles, dora
		gameTiles = DEFAULT_TILES.clone();
		Dora.clean()
		RoundDetail.kongTimes = 0;
		RoundDetail.pickedUpTilesNum = 0;
		RoundDetail.discardTiles = []
		//reset 13-tile hand
		for(var i = 0; i < players.length; i++){	//4 players
			players[i].clean();
			for(var j = 0; j < 13; j++){	//13-tile hand
				var tile = gameTiles[parseInt(Math.random() * gameTiles.length)];
				players[i].hand.push(tile);
				gameTiles.remove(tile);
				tile.owner = {
					id: i,
					name: players[i].name
				}
				ActionMessage(ActionMessage.AddTile, players[i], tile);
			}
			Hand.sort(players[i].hand);
		}
		//set dora
		Dora.addDora();
		
		
		//Let the dealer start the round
		Game.turn = RoundDetail.dealer;
		players[RoundDetail.dealer].pickUp();
		players[RoundDetail.dealer].run();

		isRoundFinish = false;
		isDealerWin = false;
		isAbortiveDraw = false;
		isDealerMakeAbortiveDraw = false;
		mutiRonPlayer = [];
		scorePositionOfStart = [];

	};
	/**
	 * Make chage of the score of each player.
	 */
	function _countScore(scoreresult, winner, playerDiscardedAgariTile){
		if((playerDiscardedAgariTile && !(playerDiscardedAgariTile instanceof Player)) ||
		   playerDiscardedAgariTile == winner){
			playerDiscardedAgariTile = null
		}
		var isDealer = (winner.id == RoundDetail.dealer);
		isDealerWin = isDealer;

		if(!playerDiscardedAgariTile){
			//Self Drawn
			players.forEach(function(val, index, arr){
				if(val == winner) return;
				var paidScore = scoreresult.score;
				if(isDealer){
					paidScore = paidScore * players.length * 1.5 / (players.length - 1);
				}
				if(val.id == RoundDetail.dealer){
					paidScore *= 2;
				}

				paidScore = Math.ceil(paidScore / 100) * 100;
				paidScore += RoundDetail.counterSticks * Rules.Score_Per_Sticks;
				val.score -= paidScore;
				return new ActionMessage(ActionMessage.PayScore, val, null, null, {score: paidScore, ScoreResult: scoreresult, isDealer: (val.id == RoundDetail.dealer)});
			});
			var getScore = scoreresult.score * (players.length - 1);
			if(isDealer){
				getScore *= 2
			}else{
				getScore += scoreresult.score;
			}
			getScore += RoundDetail.counterSticks * Rules.Score_Per_Sticks * (players.length - 1);
			winner.score += getScore
			ActionMessage(ActionMessage.ReciveScore, winner, null, null, {score: getScore, ScoreResult: scoreresult, isDealer: isDealer});
		}else{
			var paidScore = scoreresult.score * 4;
			if(isDealer){
				paidScore *= 1.5;
			}
			paidScore = Math.ceil(paidScore / 100) * 100;
			paidScore += RoundDetail.counterSticks * Rules.Score_Per_Sticks * (players.length - 1);
			winner.score += paidScore;
			playerDiscardedAgariTile.score -= paidScore;

			ActionMessage(ActionMessage.PayScore, playerDiscardedAgariTile, null, null, {score: paidScore, ScoreResult: scoreresult, isDealer: (playerDiscardedAgariTile.id == RoundDetail.dealer)});
			ActionMessage(ActionMessage.ReciveScore, winner, null, null, {score: paidScore, ScoreResult: scoreresult, isDealer: isDealer});
		}
		//Recycle the richi counter
		players.forEach(function(val, index, arr){
			if(val.isRichi || val.isDoubleRichi){
				winner.score += 1000;
				ActionMessage(ActionMessage.ReciveScore, winner, null, null, {score: 1000});
			}
		});
	}
	/**
	 * To star new game.
	 */
	this.startNewGame = function(){
		RoundDetail.dealer = Math.floor(Math.random() * players.length)
		setTiles(DEFAULT_TILES);
		startNewRound();
	};
	/**
	 * After finish the round, setting up the change of the score.
	 * @see RoundDetail
	 * @param {ScoreResult}	scoreresult
	 * @param {Player}	winner
	 * @param {Player}	[playerDiscardedAgariTile]
	 */
	this.countScore = function(scoreresult, winner, playerDiscardedAgariTile){
		if(isRoundFinish && !Rules.Multiple_Agari){
			// When only support one player be Roned
			return false;
		}else if(Rules.Multiple_Agari && mutiRonPlayer.indexOf(winner) >= 0){
			return false;
		}
		if(!scoreresult || !winner || !(scoreresult instanceof ScoreResult) || !(winner instanceof Player) || (playerDiscardedAgariTile && !(playerDiscardedAgariTile instanceof Player))){
			// The variable has some issue
			return false;
		}
		if(Rules.Multiple_Agari){
			mutiRonPlayer.push(winner);
			if(scorePositionOfStart.length == 0){
				// The first ron player
				players.forEach(function(val){
					scorePositionOfStart.push(val.score);
				});
				_roundDetail = {
					number: RoundDetail.number,
					code: RoundDetail.code,
					counterSticks: RoundDetail.counterSticks,
					dealer: RoundDetail.dealer
				}
			}
		}


		if(mutiRonPlayer.length == 3 && Rules.Triple_Ron){
			//If this part run, it meas other 2 winning players have aleady count score.
			for(var i = 0; i < players.length; i++){
				players[i].score = scorePositionOfStart[i];
			}
			//Therefore
			Game.AbortiveDraws.tripleRon()
		}else{
			_countScore(scoreresult, winner, playerDiscardedAgariTile);
		}
		isRoundFinish = true;
		return true;
	};
	/**
	 * Normal draw.
	 */
	this.drawRound = function(){
		if(RoundDetail.pickedUpTilesNum < bgColor="transparent" || isRoundFinish){
			return false
		}
		var readyPlayers = [],		//the player who has ready hand
		    nagashiPlayers = [];	//the player who can 流し満貫
		players.forEach(function(_player, index, arr){
			if(_player.isReadyHand.length > 0){
				readyPlayers.push(_player);
			}
			var canNagashi = true;
			_player.discardTiles.forEach(function(_tile, _index, _arr){
				if(!_tile.isYaoChuuTile || _tile.taker){
					canNagashi = false;
				}
			});
			if(canNagashi){
				nagashiPlayers.push(_player);
			}
		});
		if(nagashiPlayers.length > 0){
			var sr = new ScoreResult;
			sr.score = 2000; sr.yakus = ["\u6d41\u3057\u6e80\u8cab"];
			nagashiPlayers.forEach(function(val, index, arr){
				_countScore(sr, val);
			});
			isRoundFinish = true;
			isDealerWin = nagashiPlayers.indexOf(players[RoundDetail.dealer]) >= 0;
			return sr;
		}else if(readyPlayers.length > 0 ){
			if(readyPlayers.length != players.length){
				var totalPaidScore = (players.length - 1) * 1000,
				    paidScore = totalPaidScore / (players.length - readyPlayers.length), //the score paid to each player with ready hand.
				    getScore = totalPaidScore / readyPlayers.length;	//the score each player with ready hand get.
				players.forEach(function(val, index, arr){
					if(readyPlayers.indexOf(val)>=0){
						val.score += getScore;
						ActionMessage(ActionMessage.ReciveScore, val, null, null, {score: getScore ,isDealer: (val.id == RoundDetail.dealer)});
					}else{
						val.score -= paidScore;
						ActionMessage(ActionMessage.PayScore, val, null, null, {score: paidScore, isDealer: (val.id == RoundDetail.dealer)});
					}
				});
			}
		}
		//if dealer has ready hand
		if(readyPlayers.indexOf(players[RoundDetail.dealer]) >= 0){
			isDealerWin = true;
		}
		isRoundFinish = true;
	};
	/**
	 * Finish the round and start a new round.
	 * @see Game-isDealerWin
	 * @see Game-startNewRound
	 */
	this.finishRound = function(){
		if(!isRoundFinish){
			return false;
		}
		if(isDealerWin || isDealerMakeAbortiveDraw){
			RoundDetail.counterSticks++;
		}else{
			RoundDetail.number++;
			if(RoundDetail.number > players.length){
				RoundDetail.number = 1;
				RoundDetail.code++;
			}
			RoundDetail.dealer = (RoundDetail.dealer + 1) % players.length;
			if(isAbortiveDraw){
				// abortive draw
				RoundDetail.counterSticks++;
			}else{
				// not draw
				RoundDetail.counterSticks = 0;
			}
		}
		console.log(RoundDetail.round_name);
		startNewRound();
		return true;
	};

	/**
	 * Abortive draws.
	 * @namespace
	 */
	this.AbortiveDraws = new function(){
		/**
		 * The default callback function of the abortive draw.
		 */
		function defaultCallback(draw_type){
			alert(draw_type)
		}
		this.__defineSetter__("callback", function(callback_function){
			if(typeof callback_function != "function") return;
			this.__defineGetter__(callback, callback_function);
		});
		this.__defineGetter__("callback", defaultCallback);

		/**
		 * 四開槓／四槓流局
		 * @return {ActionMessage|Boolean}
		 */
		this.fourKongs = function(){
			if(RoundDetail.kongTimes != 4){
				return false;
			}
			var can4Kongs = true;
			players.forEach(function(p){
				if(!can4Kongs)
					return;
				var kongTimes = 0;
				p.meld.forEach(function(m){
					if(m.type == MeldType.KONG){
						kongTimes++;
					}
				});
				if(kongTimes == RoundDetail.kongTimes){
					can4Kongs = false;
				}
			});
			if(!can4Kongs)
				return false;
			isRoundFinish = true;
			isAbortiveDraw = true;
			Game.AbortiveDraws.callback("四槓流局");
			return new ActionMessage(ActionMessage.AbortiveDraw, null, null, null, {type: "四槓流局"})
		}
		/**
		 * 四家立直
		 * @return {ActionMessage|Boolean}
		 */
		this.fourRichis = function(){
			if(!Rules.Four_Richies){
				return false;
			}
			//check 4 richies
			var isAllRichi = true;
			players.forEach(function(val, index, arr){
				if(!isAllRichi){
					return;
				}
				if(!val.isRichi && ! val.isDoubleRichi){
					isAllRichi = false;
				}
			});
			if(!isAllRichi){
				return false;
			}
			//finish checking
			isRoundFinish = true;
			isAbortiveDraw = true;
			Game.AbortiveDraws.callback("四家立直");
			return new ActionMessage(ActionMessage.AbortiveDraw, null, null, null, {type: "四家立直"});
		}
		/**
		 * 四風連打
		 * @return {ActionMessage|Boolean}
		 */
		this.fourWinds = function(){
			if(Game.turn != 4){
				return false
			}
			var is4Wind = true, _4wind = null;
			players.forEach(function(p,index, arr){
				if(p.discardTiles.length != 1 ||
				   p.meld.length != 0 ||
				   p.discardTiles[0].type != MahjongType.Word ||
				   p.discardTiles[0].wordNum == MahjongWordNumCode.Haku ||
				   p.discardTiles[0].wordNum == MahjongWordNumCode.Hatsu ||
				   p.discardTiles[0].wordNum == MahjongWordNumCode.Chun ||
				   !is4Wind
				   ){
					is4Wind = !1;
					return;
				}
				if(!_4wind){
					_4wind = p.discardTiles[0];
				}else if(!_4wind.isSameTile(p.discardTiles[0])){
					is4Wind = !1;
					return;
				}
			});
			if(!is4Wind){
				return false;
			}
			// if it can be 四風連打
			isRoundFinish = true;
			isAbortiveDraw = true;
			Game.AbortiveDraws.callback("四風連打");
			return new ActionMessage(ActionMessage.AbortiveDraw, null, null, null, {type: "四風連打"})
		}
		/**
		 * 三家和／トリプルロン
		 * @see Rules.Multiple_Agari
		 * @see Rules.Triple_Ron
		 * @return {ActionMessage|Boolean}
		 */
		this.tripleRon = function(){
			if(!Rules.Multiple_Agari || !Rules.Triple_Ron || mutiRonPlayer.length != 3){
				return false;
			}
			//finish checking
			isRoundFinish = true;
			isAbortiveDraw = true;
			Game.AbortiveDraws.callback("三家和");
			return new ActionMessage(ActionMessage.AbortiveDraw, null, null, null, {type: "三家和"});
		}
		/**
		 * To check whether current the player's hand can make 九種九牌
		 * @return {Boolean}
		 */
		this.canNineYaokyuTiles = function(){
			if(Game.turn > 4){
				return false;
			}
			var isAllPlayerDoorClear = true;
			for(var i = RoundDetail.dealer, j = 0; j < players.length ; i = (i + 1) % players.length, j++){
				if(players[i].meld.length > 0 || (i != Game.turn && players[i].discardTiles.length > 1)){
					isAllPlayerDoorClear = false;
				}
				if(i == Game.turn || !isAllPlayerDoorClear){
					break;
				}
			}
			if(!isAllPlayerDoorClear){
				return false;
			}

			var h = players[Game.turn].hand.clone(), result = [];
			Hand.sort(h);
			h.forEach(function(_tile, arr, index){
				if(_tile.typeCode == MahjongType.Word){
					if(result.indexOf(_tile.tileName) == -1){
						result.push(_tile.tileName);
					}
				}else if(_tile.simpleNum == 1 || _tile.simpleNum == 9){
					if(result.indexOf(_tile.tileName) == -1){
						result.push(_tile.tileName);
					}
				}
			});
			if(result.length >= 9){
				return true;
			}
			return false;
		}
		/**
		 * 九種九牌／キューシュキューハイ
		 * @see Game.AbortiveDraws.canNineYaokyuTiles
		 * @return {ActionMessage|Boolean}
		 */
		this.nineYaokyuTiles = function(){
			//check 九種九牌
			if(!Game.AbortiveDraws.canNineYaokyuTiles()){
				return false;
			}
			//finish checking
			isRoundFinish = true;
			isAbortiveDraw = true;
			if(Game.turn == RoundDetail.dealer){
				isDealerMakeAbortiveDraw = true;
			}
			Game.AbortiveDraws.callback("九種九牌");
			return new ActionMessage(ActionMessage.AbortiveDraw, null, null, null, {type: "九種九牌"});
		}
	};
}
/**
 * @namespace
 */
var Hand = new function(){
	/**
	 * @constructor
	 * @this {TileArr}
	 * @param {MahjongTile[]} tilehand
	 * @property {MahjongTile[][9]}	Pin	筒子
	 * @property {MahjongTile[][9]}	Sou	索子
	 * @property {MahjongTile[][9]}	Wan	萬子
	 * @property {MahjongTile[]}	East	東
	 * @property {MahjongTile[]}	South	南
	 * @property {MahjongTile[]}	West	西
	 * @property {MahjongTile[]}	North	北
	 * @property {MahjongTile[]}	Haku	白
	 * @property {MahjongTile[]}	Hatsu	發
	 * @property {MahjongTile[]}	Chun	中
	 */
	function TileArr(tilehand){
		this.Pin = [[],[],[],[],[],[],[],[],[]];
		this.Sou = [[],[],[],[],[],[],[],[],[]];
		this.Wan = [[],[],[],[],[],[],[],[],[]];
		this.East = [];
		this.West = [];
		this.South = [];
		this.North = [];
		this.Haku = [];
		this.Hatsu = [];
		this.Chun = []
		var t = this;
		if(tilehand && tilehand instanceof Array){
			tilehand.forEach(function(tile, index, arr){
				if(!(tile instanceof MahjongTile))
					return;
				switch(tile.typeCode){
					case 0:	//餅
						t.Pin[tile.simpleNum - 1].push(tile);
						break;
					case 1:	//萬
						t.Wan[tile.simpleNum - 1].push(tile);
						break;
					case 2:	//索
						t.Sou[tile.simpleNum - 1].push(tile);
						break;
					case 3:	//字牌
						switch (tile.wordNum){
							case 0: //東
								t.East.push(tile);
								break;
							case 1:	//南
								t.South.push(tile);
								break;
							case 2:	//西
								t.West.push(tile);
								break;
							case 3:	//北
								t.North.push(tile);
								break;
							case 4:	//白
								t.Haku.push(tile);
								break;
							case 5:	//發
								t.Hatsu.push(tile);
								break;
							case 6:	//中
								t.Chun.push(tile);
								break;
						}
						break;
				}
			});
		}
	};
	TileArr.prototype.Pin = [[],[],[],[],[],[],[],[],[]];
	TileArr.prototype.Sou = [[],[],[],[],[],[],[],[],[]];
	TileArr.prototype.Wan = [[],[],[],[],[],[],[],[],[]];
	TileArr.prototype.East = [];
	TileArr.prototype.West = [];
	TileArr.prototype.South = [];
	TileArr.prototype.North = [];
	TileArr.prototype.Haku = [];
	TileArr.prototype.Hatsu = [];
	TileArr.prototype.Chun = [];
	/**
	 * clone the current TileArr
	 * @return {TileArr}
	 */
	TileArr.prototype.clone = function(){
		var t = new TileArr;
		t.Pin = this.Pin.clone();
		t.Sou = this.Sou.clone();
		t.Wan = this.Wan.clone();
		t.East = this.East.clone();
		t.West = this.West.clone();
		t.South = this.South.clone();
		t.North = this.North.clone();
		t.Haku = this.Haku.clone();
		t.Hatsu = this.Hatsu.clone();
		t.Chun = this.Chun.clone();
	}
	/**
	 * The recursive function of determining the tile array. This function only support for the tiles with word.
	 * @see Hand-countNumMeld
	 */
	var countWordMeld = function(/** MahjongTile[]  */ tilearr, /** [HandInfo[]] */ resultarr, /** [HandInfo] */ handinfo){
		if(!tilearr || !(tilearr instanceof Array) )
			return false;
		if(!resultarr || !(resultarr instanceof Array))
			resultarr = [];
		if(!handinfo || !(handinfo instanceof HandInfo)){
			handinfo = new HandInfo
		}
		var _test_tiles, h, haspair = false;
		switch(tilearr.length){
			default:
			case 3:
				//視為刻子
				h = handinfo.clone()
				_test_tiles = tilearr.splice(0,3)
				h.concealedTriplets.push(_test_tiles);
				resultarr.push(h);
				tilearr.splice(0,0, _test_tiles[0], _test_tiles[1], _test_tiles[2]);
			case 2:
				// 視為對子
				haspair = true
				h = handinfo.clone()
				_test_tiles = tilearr.splice(0,2);
				h.pairs.push(_test_tiles);
				resultarr.push(h);
				//tilearr.splice(0,0, _test_tiles[0], _test_tiles[1]);

			case 1:
				// unused
				if(!haspair){
					h = handinfo.clone()
				}
				while(tilearr.length){
					h.unused.push(tilearr.splice(0,1)[0])
				}
				if(!haspair)
					resultarr.push(h);
			case 0:
				break;
		}
		return resultarr
	}
	/**
	 * The recursive function of determining the tile array. This function only support for the tiles with number.
	 * @return {HandInfo[] | Boolean}
	 * @see Hand-countWordMeld
	 */
	var countNumMeld = function(/** MahjongTile[][9]  */ tilearr, /** [HandInfo[]] */ resultarr, /** [HandInfo] */ handinfo){
		var _is_top = false, is_bottom = true;

		if(!tilearr || !(tilearr instanceof Array) )
			return false;
		if(!resultarr || !(resultarr instanceof Array))
			resultarr = [];
		if(!handinfo || !(handinfo instanceof HandInfo)){
			_is_top = true;
			is_bottom = false;
			handinfo = new HandInfo
		}
		for(var i = 0; i < tilearr.length; i++){
			var tiles = tilearr[i];

			// 視為刻子
			if(tiles.length >= 3){
				var _test_tiles = tiles.splice(0,3),	//進行判斷的tiles
				    h = handinfo.clone();
				h.concealedTriplets.push(_test_tiles);
				var r = countNumMeld(tilearr.slice(0,tilearr.length), resultarr, h);
				if(r == false){
					is_bottom = false;
				}
				//put tiles back
				tilearr[i].splice(0,0, _test_tiles[0], _test_tiles[1], _test_tiles[2]);
			}

			// 視為順子
			if( i+2 < tilearr.length &&
				tilearr[i].length >= 1 && tilearr[i+1].length >= 1 && tilearr[i+2].length >= 1 ){
				var _test_tiles = [
					tilearr[i].splice(0,1)[0],
					tilearr[i+1].splice(0,1)[0],
					tilearr[i+2].splice(0,1)[0]
				],
				    h = handinfo.clone();

				h.straight.push(_test_tiles);
				var r = countNumMeld(tilearr.slice(0,tilearr.length), resultarr, h);
				if(r == false){
					is_bottom = false;
				}
				tilearr[i].push(_test_tiles[0])
				tilearr[i+1].push(_test_tiles[1])
				tilearr[i+2].push(_test_tiles[2])
			}

			// 視為對子
			if(tiles.length >= 2){
				var _test_tiles = tiles.splice(0,2),
				    h = handinfo.clone();
				h.pairs.push(_test_tiles);
				var r = countNumMeld(tilearr.slice(0,tilearr.length), resultarr, h);
				if(r == false){
					is_bottom = false;
				}
				tiles.splice(0,0, _test_tiles[0], _test_tiles[1])
			}

			//順子／對子／刻子的判斷完畢，假設為無需使用的牌
			if(tiles.length>0){
				tiles.forEach(function(val, index, arr){
					handinfo.unused.push( val )
				})
				tilearr[i] = []
			}
		}
		if(_is_top || is_bottom){
			var _arr = []
			for(var i = handinfo.unused.length - 1; i > 0; i--){
				var _t1 = handinfo.unused[i],
				    _t2 = handinfo.unused[i-1];
				switch(Math.abs(_t1.simpleNum - _t2.simpleNum)){
					case 1:
					case 2:
						_arr.push( handinfo.unused.splice(i-1, 2) )
						i--;
						break;
					default:
						break;
				}
			}
			for(i  = 0 ; i < _arr.length; i++){
				handinfo.unused.push(_arr[i])
			}

			resultarr.push(handinfo)

			if(_is_top)
				return resultarr
			else if(is_bottom)
				return true
		}else{
			return false
		}
	}

/**
 * Sort the MahjongTile array &ldquo;h&rdquo;
 * @param {MahjongTile[]}h
 * @see Player#hand
 * @return {MahjongTile[]} The sorted result.
 */
	this.sort = function(h){
		if(!(typeof(h)=='object'&&(h instanceof Array) )) return;
		while(h.indexOf(undefined)>= 0)
			h.remove(undefined);
		while(h.indexOf(null)>= 0)
			h.remove(null);
		var sortMahjong = function (a,b){
			if(a.typeCode != b.typeCode){
				return a.typeCode - b.typeCode;
			}else if(a.simpleNum != b.simpleNum){
				return a.simpleNum - b.simpleNum;
			}else{
				return a.wordNum - b.wordNum;
			}
		}
		h.sort(sortMahjong);
		return h;
	}

/**
 * To count the numbers of concealedTriplets (暗刻), pairs(對子), and straight(順子) in the hand
 * @param {MahjongTile[]} hand
 * @see Player#hand
 * @return {HandInfo}
 */
	this.countMeldsInHand = function(hand){
		if(!(hand instanceof Array))
			return;
		var _h = hand,
		    index,
		    hand_style = new TileArr(_h);

		var p = countNumMeld(hand_style.Pin),
		    s = countNumMeld(hand_style.Sou),
		    w = countNumMeld(hand_style.Wan),
		    east = countWordMeld(hand_style.East),
		    west = countWordMeld(hand_style.West),
		    south = countWordMeld(hand_style.South),
		    north = countWordMeld(hand_style.North),
		    haku = countWordMeld(hand_style.Haku),
		    hatsu = countWordMeld(hand_style.Hatsu),
		    chun = countWordMeld(hand_style.Chun);

		var can_done_array = [], list = [p,s,w,east,west,south,north,haku,hatsu,chun];
		list.forEach(function(val, index, array){
			if(val.length > 0){
				can_done_array.push(val)
			}
		})
		var resultArray = [];

		var setup_result = function(handinfo, resultarr, resultarrindex){
			if(resultarrindex >= resultarr.length){
				return
			}
			for(var i = 0; i < resultarr[resultarrindex].length; i++){
				var h = handinfo.join(resultarr[resultarrindex][i]);
				if(resultarrindex + 2 <= resultarr.length){
					setup_result(h, resultarr, resultarrindex + 1);
				}else if(resultarrindex = resultarr.length - 1){
					resultArray.push(h)
				}else{
					continue;
				}
			}
		}
		setup_result(new HandInfo ,can_done_array, 0)

		var result = []
		resultArray.forEach(function(val, index, array){
			//Debug, sometimes numbers of tiles in HandInfo != hand.length
			if((!val instanceof HandInfo))
				return
			var tile_length = val.concealedTriplets.length * 3 +
					val.pairs.length * 2 +
					val.straight.length * 3;
			for(var i = 0; i < val.unused.length; i++){
				if(val.unused[i] instanceof Array){
					tile_length += val.unused[i].length
				}else if(val.unused[i] instanceof MahjongTile){
					tile_length += 1
				}
			}
			if(tile_length == hand.length){
				result.push(val)
			}
		})
		return result;
	};

/**
 * To check whether the HandInfo is ready
 * @param {HandInfo} info
 * @see Hand.countMeldsInHand
 * @see Hand.isFinishHand
 * @return {Boolean}
 */
	this.isReadyHand = function(info){
		if(!(info instanceof HandInfo))
			return false
		var tile_length = info.concealedTriplets.length * 3 +
				info.pairs.length * 2 +
				info.straight.length * 3;
		for(var i = 0; i < info.unused.length; i++){
			if(info.unused[i] instanceof Array){
				tile_length += info.unused[i].length
			}else if(info.unused[i] instanceof MahjongTile){
				tile_length += 1
			}
		}

		if(tile_length % 3 == 2)
			return this.isFinishHand(info);
		else if(tile_length % 3 != 1)
			return false;
		if(info.unused.length == 1 && info.pairs.length <= 1){
			//info.pairs.length == 1 => 為順子 / 刻子缺一張
			//info.pairs.length == 0 => 對子缺一張，單騎
			return true
		}
		if(info.unused.length == 0 && info.pairs.length == 2){
			// e.g. 🀋🀋🀏🀏🀙🀚🀛
			return true
		}
		if(tile_length == 13 && info.pairs.length == 6){
			// 7 pairs
			return true
		}
		if(tile_length == 13 &&
			((info.pairs.length == 1 && info.unused.length == 11) ||
			 (info.pairs.length == 0 && info.unused.length == 13)) ){
			//国士無双
			var a = info.unused.clone()
			for(var i = 0; i < info.pairs.length; i++){
				for(var j = 0; j < info.pairs[i].length; j++){
					a.push(info.pairs[i][j])
				}
			}
			var all_9 = true
			a.forEach(function(val, index, arr){
				if(all_9 == false)
					return
				if(!(val.typeCode == 3 || (val.simpleNum == 1 || val.simpleNum == 9)))
					all_9 = false

			});
			if(all_9 == true){
				return true
			}
		}
		return false;
	}

/**
 * To check whether the HandInfo is a finish hand
 * @param {HandInfo} info
 * @see Hand.countMeldsInHand
 * @see Hand.isReadyHand
 * @return {Boolean}
 */
	this.isFinishHand = function(info){
		if(!(info instanceof HandInfo))
			return false
		var tile_length = info.concealedTriplets.length * 3 +
				info.pairs.length * 2 +
				info.straight.length * 3;
		// do not count info.unused.length, because if the hand is finish, it won't have any unused tiles.

		if(tile_length % 3 != 2)
			return false;
		if(info.unused.length != 0 && info.unused.length != 12){
			//if it is 国士無双, info.unused.length will be 12
			return false
		}
		if(info.pairs.length != 1 && info.pairs.length != 7){
			//if the hand is finished, the pair must in the hand not in the meld
			//if it is チートイツ, info.pairs.length will be 7
			return false
		}
		return true
	}
}
/**
 * This class is the detail of the list of the Mahjong tile hand.
 * @constructor
 * @see Hand.countMeldsInHand
 */
function HandInfo(){
	if(!(this instanceof HandInfo))
		return new HandInfo
	this.concealedTriplets = [];
	this.pairs= [];
	this.straight= [];
	this.unused = [];
}
/**
 * 暗刻
 * @type MahjongTile[][]
 */
HandInfo.prototype.concealedTriplets = [];
/**
 * 對子
 * @type MahjongTile[][]
 */
HandInfo.prototype.pairs= [];
/**
 * 順子
 * @type MahjongTile[][]
 */
HandInfo.prototype.straight= [];
/**
 * The tiles which is not used
 * @type MahjongTile[]
 */
HandInfo.prototype.unused = [];
/**
 * Clone the information detail
 * @return {HandInfo}
 */
HandInfo.prototype.clone = function(){
	var clonedObj = new HandInfo;
	clonedObj.concealedTriplets = this.concealedTriplets.slice(0, this.concealedTriplets.length);
	clonedObj.pairs = this.pairs.slice(0, this.pairs.length);
	clonedObj.straight = this.straight.slice(0, this.straight.length)
	clonedObj.unused = this.unused.slice(0, this.unused.length);
	return clonedObj;
}
/**
 * Mix another HandInfo class
 * @param {HandInfo} h
 * @return {HandInfo}
 */
HandInfo.prototype.join = function(h){
	if(!(h instanceof HandInfo))
		return this;
	var _this = this.clone(), _h = h.clone(), i;
	for(i = 0; i < _h.concealedTriplets.length; i++)
		_this.concealedTriplets.push(_h.concealedTriplets[i]);
	for(i = 0; i < _h.pairs.length; i++)
		_this.pairs.push(_h.pairs[i]);
	for(i = 0; i < _h.straight.length; i++)
		_this.straight.push(_h.straight[i]);
	for(i = 0; i < _h.unused.length; i++)
		_this.unused.push(_h.unused[i]);

	return _this;
}

/**
 * @constructor
 * @this {MahjongTile}
 * @param {String} t_name	the Mahjong tile's name, e.g. 南, 三餅
 * @param {String} type		the tile type, e.g. 字牌, 萬
 * @param {String} picText	the XML unicode for the tile, e.g. <span class="unicode">&#126976;</span>(&amp;#126976;)
 * @param {Boolean} [hidden=false]	 Whether this tile is a hidden tile
 * @param {String} [pic]		 picture the url of the tile picture.
 * @property {Number} id	Tile ID, it will equal the tile index in {@link DEFAULT_TILES}.
 * @property {String} tileName	the Mahjong tile's name.
 * @property {String} tilePicText	The tile's Unicode Characters. It will be set as XML Characters, such as &amp;#126976;.
 * @property {String} mahjongPic	picture url
 * @property {Boolean} [hiddenTile=false]	Whether this tile is a hidden tile. It will be set as the param hidden
 * @property {String} tileType	The type of the tile, such as "餅","萬","索","字牌".
 * @property {Number} typeCode
 * @property {Number} simpleNum
 * @property {Number} wordNum
 * @property {String} tilePicText_UTF_16	The tile's Unicode Characters. This string is the same as {@link #tilePicText} but it is a UTF-16 string, such as 0xD83C0xDC00.
 * @property {Boolean} [isRedDora=false]
 * @property {Boolean} [isRinshanTile=false]	Whether the tile is the extra tile from the end of the wall.
 * @property {Number} [pickingTileNum=0]		The number of the tiles picked up.
 * @property {Number} [jun=-1]			The 巡（jun） when the tile discard.
 * @property {Object} owner An object save the detail of the tile owner
 */
function MahjongTile(t_name, type,picText, hidden, pic, isRedDora){
	if(!t_name || !type || !picText)return;
	var chinesetext = ["一","二","三","四","五","六","七","八","九"];
	this.tileName = t_name;
	this.tilePicText = picText;
	this.hiddenTile = !!hidden;
	this.mahjongPic = pic;
	this.tileType = type;

	var tl = ["餅","萬","索","字牌"];
	var word = ["東","南","西","北","白","發","中"];
	this.typeCode = tl.indexOf(type);
	this.id = parseInt(Math.random() * 100 + 134);

	if(t_name.length == 2){
		var chNum = t_name[0];
		this.simpleNum = chinesetext.indexOf(chNum) + 1;
		this.wordNum = -1;
	}else{
		this.simpleNum = -1;
		this.wordNum = word.indexOf(t_name);
	}
	this.isRedDora = !!isRedDora;
	this.isRinshanTile = false;


	var utf16Num = (parseInt(picText.replace("&#","").replace(";","").replace(/ /g, "")) - 70656).toString(16); //&#126976; => 0xD83C 0xDC00 //50752
	var utf16Text = "";
	eval("utf16Text = \"\\uD83C\\u" + utf16Num + "\"");
	this.tilePicText_UTF_16 = utf16Text;
	var str = "[Mahjong Tile";
	str += ": \t" + t_name + "（"+ utf16Text + "）";
	if(isRedDora) str += "（Red Dora）";
	if(hidden) str+= "（hidden）";
	str+= "]";
	this.toString = function(){return str};
}
MahjongTile.prototype.pickingTileNum = 0;
MahjongTile.prototype.jun = -1;
/**
 * @function
 * @param {MahjongTile} m
 * @return  {Boolean}
 */
MahjongTile.prototype.isSameTile= function(m){
	if(!m || !(m.constructor == MahjongTile))return false;
	return (m.simpleNum == this.simpleNum) && (m.wordNum == this.wordNum) && (m.typeCode == this.typeCode);
}
/**
 * @function
 * @param {MahjongTile} m
 * @return  {Boolean}
 */
MahjongTile.prototype.isSameTileType= function(m){
	if(!m || !(m instanceof MahjongTile))return false;
	return (m.typeCode == this.typeCode);
}
MahjongTile.prototype.__defineGetter__("isGreenTile", function(){
	switch(this.typeCode){
		case MahjongType.Sou:
			switch(this.simpleNum){
				case 2:
				case 3:
				case 4:
				case 6:
				case 8:
					return true;
					break;
				default :
					return false;
			}
			break;
		case MahjongType.Word:
			if(this.wordNum == MahjongWordNumCode.Hatsu)
				return true;
			return false;
			break;
		default:
			return false
			break;
	}
});
MahjongTile.prototype.__defineGetter__("isYaoChuuTile", function(){
	if(this.typeCode == MahjongType.Word || this.simpleNum == 1 || this.simpleNum == 9){
		return true
	}
	return false;
});

/**
 * @class
 * @public
 * @this {Meld}
 * @param {String}	type	Meld type. e.g. "KONG", "CHOW"
 * @param {Mahjong}
 */
function Meld(type, taked_tile, meld_tiles){
	if(!(this instanceof Meld))
		return new Meld(type, taked_tile, meld_tiles);
	this.type = type;
	this.takedTile = taked_tile;
	this.meldTiles = meld_tiles;
}
Meld.prototype.__defineGetter__("tiles", function(){
	var tiles = this.meldTiles.clone() || [];
	tiles.push(this.takedTile);
	if(this.kongType == KongType.Added_Open_Quad){
		tiles.push(this.addKongTile);
	}

	Hand.sort(tiles);
	return tiles;
});
/**
 * The type of the meld
 * @type String
 * @see MeldType
 */
Meld.prototype.type = null;
/**
 * The type of the meld
 * @type MahjongTile
 * @see MeldType
 */
Meld.prototype.takedTile = null;
/**
 * If the meld is a Kong, it will give the detail of the type of the kong.
 * @default KongType.None
 * @type Number
 * @see KongType
 */
Meld.prototype.kongType = KongType.None;
/**
 * If the meld is Added open quad, it is the added tile.
 * @default null
 * @type MahjongTile
 */
Meld.prototype.addKongTile = null;

/**
 * @constructor
 * @public
 * @this {Player}
 * @param {String} [playerName="Unknown"] The variable to set as player's name.

 * @property {number} score Player's score.
 * @property {MahjongTile[]} hand
 * @property {MahjongTile[]} discardTiles 棄牌
 * @property {Boolean} [isRichi=false] 立直（聽）
 * @property {Boolean} [isDoubleRichi=false] W立直
 * @property {Boolean} [isDoujun=false] 同巡
 * @property {number} [richiTurn=-1] The turn when the player richi, it is used to check 一發.
 * @property {Boolean} [canOneShotTurn=false] Whether the player can 一發
 * @property {Meld[]} meld 出示牌
 * @property {MahjongTile} pickingUpTile
 * @property {Boolean} isDoorClear <!-- readonly --> Whether the player is door clear
 */
function Player(playerName){
	this["name"] = playerName? playerName.toString() : "Unknown";
	this.hand=[];
	this.discardTiles = [];
	this.meld = [];
}
/**
 * Player's name, it will be set as "playerName"
 * @type String
 */
Player.prototype.name = "";
Player.prototype.score = 0;
Player.prototype.hand = null;
Player.prototype.discardTiles = null;
Player.prototype.meld = null;
Player.prototype.isRichi= false;
Player.prototype.isDoubleRichi= false;	//雙立直
Player.prototype.isDoujun = false;	//同巡
Player.prototype.richiTurn = -1;	//一發
Player.prototype.canOneShotTurn = false;
Player.prototype.pickingUpTile = null;
/**
 * If the player makes a meld by calling for another player's discard, then this variable become true.
 * It will go back to false if the player discard a tile.
 * @type Boolean
 * @see Player#toDiscardTile
 */
Player.prototype.callingOthers = false;

Player.prototype.toString = function(){
	return "[Player " + this["name"] + "\tScore: " + this["score"] + "]";
}
/*
 * Whether the hand of the player is door clear
 */
Player.prototype.__defineGetter__("isDoorClear", function(){
	if(this.meld.length === 0)
		return true;
	var All_Closed_Quad = true;
	this.meld.forEach(function(val, index, arr){
		if(!All_Closed_Quad)
			return;
		if(val.type != MeldType.KONG ||
		   !(val.kongType == KongType.Closed_Quad_from_Hand || val.kongType == KongType.Closed_Quad_from_PickupTile))
			All_Closed_Quad = false;
	});
	return All_Closed_Quad;

});
/*
 * Play ID.
 */
Player.prototype.__defineGetter__("id", function(){
	return players.indexOf(this);
});
/*
 * 自風
 */
Player.prototype.__defineGetter__("wind", function(){
	return (players.indexOf(this) + players.length - RoundDetail.dealer) % players.length
});
/**
 * To check whether the hand is ready
 * @return  {MahjongTile[]} The tiles which is superfluous for ready hand.
 */
Player.prototype.__defineGetter__("isReadyHand", function(){
	var thisHand = this.hand;
	var h = thisHand.slice(0, thisHand.length);
	h.push(this.pickingUpTile)
	var removeableTiles = [];
	h.forEach(function(/*MahjongTile*/val, index, arr){
		var cloneArr = arr.slice(0, arr.length);
		cloneArr.removeByIndex(index);
		var handresult = Hand.countMeldsInHand(cloneArr);
		handresult.forEach(function(/*{HandInfo}*/_val, _index, _arr){
			if(removeableTiles.indexOf(val) < 0 && Hand.isReadyHand(_val)){
				removeableTiles.push(val);
			}
		});
	});

	return removeableTiles;
});
/**
 * @function
 * @param {MahjongTile} newTile Optional.
 * @see KongType
 * @return  {Number}
 */
Player.prototype.canKong = function(newTile){
	if(this.hand.length < 4) return KongType.None;
	var h = this.hand;
	if(newTile){
		//暗槓
		var i;
		//Kong Tile is the picking up tile.
		for(i= 0; i < h.length - 2; i++){
			if(h[i].isSameTile(newTile) && h[1+i].isSameTile(newTile) && h[2+i].isSameTile(newTile)){
				if(newTile == this.pickingUpTile)
					return KongType.Closed_Quad_from_PickupTile;
				else
					return KongType.Open_Quad;
			}
		}
		//加槓
		//player can kong tiles which has already pong
		for(i = 0; i < this.meld.length; i++){
			var m = this.meld[i];
			if(m.type == MeldType.PONG){
				if(m.takedTile.isSameTile(newTile))
					return KongType.Added_Open_Quad;
			}
		}
	}else{
		//kong tile is in the hand
		var _h = h.clone();
		if(this.pickingUpTile)
			_h.push(this.pickingUpTile);
		Hand.sort(_h);
		for(var i= 0; i < _h.length - 1; i++){
			if(i < _h.length - 4 &&
				_h[i].isSameTile(_h[1+i]) &&
				_h[i].isSameTile(_h[2+i]) &&
				_h[i].isSameTile(_h[3+i]))
				return KongType.Closed_Quad_from_Hand;
		}
	}
	return KongType.None;
}
/**
 * @function
 * @param {MahjongTile} newTile
 * @return  {Boolean}
 */
Player.prototype.canPong = function(newTile){
	if(this.hand.length < 3) return false;
	var h = this.hand;
	for(var i= 0; i < h.length - 1; i++){
		if(h[i].isSameTile(newTile) && h[1+i].isSameTile(newTile))
			return true;
	}
	return false;
}
/**
 * @function
 * @param {MahjongTile} newTile
 * @return  {Boolean}
 */
Player.prototype.canChow = function(newTile){
	if(this.hand.length < 3) return false;
	var h = this.hand;
	for(var i= 1; i < h.length - 1; i++){
		if(h[i].isSameTileType(newTile)){
			try{
			if(h[i+1].isSameTileType(newTile) && h[i+1].simpleNum == newTile.simpleNum+1 &&
			   h[i+2].isSameTileType(newTile) && h[i+2].simpleNum == newTile.simpleNum+2)
				return true;
			}catch(e){}
			if(h[i+1].isSameTileType(newTile) && h[i+1].simpleNum == newTile.simpleNum+1 &&
			   h[i-1].isSameTileType(newTile) && h[i-1].simpleNum == newTile.simpleNum-1)
				return true;
			try{
			if(h[i-1].isSameTileType(newTile) && h[i-1].simpleNum == newTile.simpleNum-1 &&
			   h[i-2].isSameTileType(newTile) && h[i-2].simpleNum == newTile.simpleNum-2)
				return true;
			}catch(e){}
		}
	}
	return false;
}
/**
 * Clean all tiles in a player's hand
 * @see Game-startNewRound
 */
Player.prototype.clean = function(){
	this.hand=[];
	this.discardTiles = [];
	this.meld = [];
	this.isRichi=!1;
	this.isDoubleRichi=!1;	//雙立直
	this.isDoujun = !0;
	this.richiTurn = -1;	//一發
	this.canOneShotTurn = false;
	this.pickingUpTile=null;
	this.callingOthers = false;
}
/**
 * Pick up new tile
 * @see gameTiles
 * @see Player#pickingUpTile
 * @return {ActionMessage}
 */
Player.prototype.pickUp= function(){
	if(this.pickingUpTile || Game.turn != this.id ) return;
	if(this.callingOthers) return;
	if(RoundDetail.pickedUpTilesNum > bgColor="transparent"){
		//Round finish, Checking any winner
		return;
	}
	var index = parseInt(Math.random() * gameTiles.length);
	this.pickingUpTile = gameTiles[index];
	gameTiles.removeByIndex(index);
	var t = this;
	this.pickingUpTile.owner = {
		id: t.id,
		name: t.name
	};
	RoundDetail.pickedUpTilesNum++;
	this.pickingUpTile.pickingTileNum = RoundDetail.pickedUpTilesNum;
	return new ActionMessage(ActionMessage.PickUp, t, this.pickingUpTile)
}
/**
 *
 * @see Player#isReadyHand
 * @see Player#isRichi
 * @see Player#isDoubleRichi
 * @see Player#richiTurn
 * @see Player#canOneShotTurn
 * @return {ActionMessage|Boolean}
 */
Player.prototype.richiAction=function(){
	if(!this.isDoorClear ||
	   this.isReadyHand.length == 0 ||
	   this.isDoubleRichi || this.isRichi ||
	   this.score < 1000){
		return false;
	}
	if(RoundDetail.pickedUpTilesNum < players.length){
		//check whether player can W richi
		var canWRichi = true
		for(var i = this.index; i >= 0; i--){
			if(players[i].meld.length!=0){
				canWRichi = false;
				break;
			}
		}
		if(canWRichi){
			this.isDoubleRichi = true;
		}
	}
	this.isRichi = true;
	this.canOneShotTurn = true;
	this.richiTurn = RoundDetail.pickedUpTilesNum;
	this.score -= 1000;
	return new ActionMessage(ActionMessage.Richi, this, null, null,{isDoubleRichi:this.isDoubleRichi});
}
/**
 * To discard a tile from hand or the picking up tile
 * @param {MahjongTile|number} [d=pickingUpTile]. <br />
 * 	If it is a MahjongTile, it should be the tile in the hand or the picking up tile. <br/>
 * 	If d is number. It should be the index of the tile which is in the hand. Otherwise, if d is -1, it will be seen as the picking up tile.<br />
 * 	If d is null, then the program will set it as the pickingUpTile of the player.
 * @see Player#hand
 * @see Player#pickingUpTile
 * @see Hand.sort
 * @return {ActionMessage|Boolean}
 */
Player.prototype.toDiscardTile = function(d){
	if(!this.pickingUpTile){
		return;
	}
	if(this.isRichi || this.isDoubleRichi){
		if(this.richiTurn == RoundDetail.pickedUpTilesNum){
			if(this.isReadyHand.indexOf(d) < 0){
				// in richi turn, still can discard any tiles.
				return false;
			}else{
				d.isRichiTile = true;
			}
		}else{
			d = this.pickingUpTile;
		}
	}
	if(typeof d == "object" && d != this.pickingUpTile && this.hand.indexOf(d) == -1)return;
	if(typeof d == "number"){
		if(d >= this.hand.length) return;
		if(d >= 0){
			d = this.hand[d];
		}else{
			d = this.pickingUpTile;
		}
	}
	d = d || this.pickingUpTile;

	this.discardTiles.push(d);
	RoundDetail.discardTiles.push(d);
	if(d == this.pickingUpTile){
		this.pickingUpTile = null;
	}else{
		this.hand.remove(d);
		this.hand.push(this.pickingUpTile);
		this.pickingUpTile = null;
		Hand.sort(this.hand);
	}
	this.callingOthers = 0;
	//To run AI
	//check whether can pong / kong / chow / agari
	var nt = Game.turn;
	for(var i = (Game.turn + 1) % players.length; i != Game.turn; i = (i + 1) % players.length){
		players[i].run();
		if(nt != Game.turn){
			players[Game.turn].run();
			return;
		}
	}
	//if not finish round, run next player's AI
	Game.turn = (Game.turn + 1) % players.length;

	ActionMessage(ActionMessage.Discard, this, d, null, (d.isRichiTile? {isRichiTile: true}: null) );

	if(RoundDetail.pickedUpTilesNum == bgColor="transparent"){
		Game.drawRound();
	}else{
		players[Game.turn].run();
	}
}
/**
 * @see Player#canPong
 * @param {MahjongTile}pongTile
 * @return {ActionMessage}
 */
Player.prototype.pongAction=function(pongTile){
	if(!this.canPong(pongTile))return;
	var t = this;
	pongTile.taker = {
		id: t.id,
		name: t.name
	};

	var h = [];
	this.hand.forEach(function(val,index, arr){
		if(val.isSameTile(pongTile)){
			h.push(val);
		}
	});
	h.forEach(function(val,index, arr){
		if(val.isSameTile(pongTile)){
			t.hand.remove(val);
		}
	})

	var a = new Meld(MeldType.PONG, pongTile, h)
	this.meld.push(a)
	Game.turn = this.id;
	this.callingOthers = 1;

	//remove each person's OneShotTurn
	for(var _ = 0; _ < players.length; _++){
		if(players[_]== this) continue;
		players[_].canOneShotTurn=false;
	}
	this.makeDoujun();
	//when this function run, turn  is not this.
	Game.turn = this.id;
	
	return new ActionMessage(ActionMessage.Pong, this, pongTile, players[pongTile.owner]);
}
/**
 * @see Player#canKong
 * @see Dora.addDora
 * @see KongType
 * @param {MahjongTile}t
 */
Player.prototype.kongAction=function(t){
	var canKong = this.canKong(t);
	if(!canKong)return;
	if(RoundDetail.kongTimes > 4) return;
	var theTileIsFromHand = (t == this.pickingUpTile || this.hand.indexOf(t) >= 0);
	//set up details of the taker
	var _this = this;
	t.taker = {
		id: _this.id,
		name: _this.name,
		is_self: theTileIsFromHand
	};
	switch(canKong){
		case KongType.Closed_Quad_from_Hand:
			this.hand.remove(t);
			if(this.pickingUpTile)
				this.hand.push(this.pickingUpTile);
		case KongType.Closed_Quad_from_PickupTile:
			this.pickingUpTile = null;
		case KongType.Open_Quad:
			var meld_tiles = [];
			this.hand.forEach(function(val,index, arr){
				if(t.isSameTile(val)){
					meld_tiles.push(val);
				}
			});

			meld_tiles.forEach(function(val,index, arr){
				_this.hand.remove(val);
			});

			var a = new Meld(MeldType.KONG, t, meld_tiles);
			a.kongType = canKong
			this.meld.push(a);
			break;
		case KongType.Added_Open_Quad:
			this.meld.forEach(function(val, index, arr){
				if(val.type == MeldType.PONG && val.meldTiles[0].isSameTile(t)){
					val.type = MeldType.KONG;
					val.addKongTile = t;
				}
			})
			break;
		case KongType.None:
			return;
	}

	Dora.addDora();
	RoundDetail.kongTimes++;


	this.callingOthers = true;
	//remove each person's OneShotTurn
	for(var _ = 0; _ < players.length; _++){
		if(players[_]== this) continue;
		players[_].canOneShotTurn=false;
	}
	
	this.makeDoujun();
	//when this function run, turn  is not this.
	Game.turn = this.id;

	//add a tile from Mountain.
	var index = parseInt(Math.random() * gameTiles.length);
	this.pickingUpTile = gameTiles[index];
	gameTiles.removeByIndex(index);
	this.pickingUpTile.owner = {
		id: _this.id,
		name: _this.name
	};
	this.pickingUpTile.isRinshanTile = true;	//set it is リーシャン tile

	
	ActionMessage(ActionMessage.Kong, _this, t, (t.owner == this.id ? null: players[t.owner]),{kongType: canKong});
	ActionMessage(ActionMessage.PickUp, _this, this.pickingUpTile, null, {isRinshanTile: true});
}
/**
 * @param {MahjongTile} t
 * @param {Number[]} $ The indexes which can be chowed with "t"
 * @see Player#canChow
 * @see Player#chowChoise
 * @see Player#meld
 * @return {ActionMessage|Boolean}
 */
Player.prototype.chowAction=function(t, $){
	if(!this.canChow(t) || !$ || !t || !(($ instanceof Array) && $.length == 2) )return false;
	var this_index = this.id
	if(players[ (this_index + 4-1) % players.length].discardTiles.lastItem() != t) return false;

	var _this = this;
	t.taker = {
		id: _this.id,
		name: _this.name
	};

	$.sort();
	var hand = this.hand, choise = this.chowChoise(t);
	for(var i = 0; i < choise.length; i++){
		choise[i].sort();
		if($[0] == choise[i][0] && $[1] == choise[i][1]){
			var a = new Meld(MeldType.CHOW, t, [hand[ $[0] ], hand[ $[1] ]])
			hand.removeByIndex($[1]);
			hand.removeByIndex($[0]);
			this.meld.push(a);
			Game.turn = this.id;
			this.callingOthers = true;

			//remove each person's OneShotTurn
			for(var _ = 0; _ < players.length; _++){
				if(players[_]== this) continue;
				players[_].canOneShotTurn=false;
			}
			
			this.makeDoujun();
			//when this function run, turn  is not this.
			Game.turn = this.id;
			return new ActionMessage(ActionMessage.Chow, _this, t, players[t.owner.id], {chowChoise: $});
		}
	}
	return false;
}
/**
 * To get the tiles which can be chow in hand.
 * @return {Number[][]} The number array for the indexes of the tiles which can be chowed with "t".<br />
	This array will like this : [[1,2],[2,3]]. <br />
 	In this example. It means that the tile "t" can be chowed with both hand[1] and hand[2] or both hand[2] and hand[3].<br />
	For instence
		<ul style="padding-left: 20px;list-style-type: circle;">
			<li>t is Bamboo 6(<span class="unicode">🀞</span>)</li>
			<li>hand[<span class="NUMB">1</span>] is Bamboo 4(<span class="unicode">🀜</span>)</li>
			<li>hand[<span class="NUMB">2</span>] is Bamboo 5(<span class="unicode">🀝</span>)</li>
			<li>hand[<span class="NUMB">3</span>] is Bamboo 7(<span class="unicode">🀟</span>).</li>
		</ul>
 * @see Player#canChow
 * @param {MahjongTile}t
 */
Player.prototype.chowChoise = function(t){
	if(!this.canChow(t))return false;
	var h = this.hand;
	var result = []
	for(var i= 1; i < h.length - 1; i++){
		if(h[i].isSameTileType(t)){
			try{
			if(h[i+1].isSameTileType(t) && h[i+1].simpleNum == t.simpleNum+1 &&
			   h[i+2].isSameTileType(t) && h[i+2].simpleNum == t.simpleNum+2)
				result.push([i+1],[i+2]);
			}catch(e){}
			if(h[i+1].isSameTileType(t) && h[i+1].simpleNum == t.simpleNum+1 &&
			   h[i-1].isSameTileType(t) && h[i-1].simpleNum == t.simpleNum-1)
				result.push([i-1,i+1]);
			try{
			if(h[i-1].isSameTileType(t) && h[i-1].simpleNum == t.simpleNum-1 &&
			   h[i-2].isSameTileType(t) && h[i-2].simpleNum == t.simpleNum-2)
				result.push([i-1,i-2]);
			}catch(e){}
		}
	}
	return result;
}
/**
 * @return {MahjongTile[]}	the tiles which other players can see.
 */
Player.prototype.getHand = function(){
	var h = this.hand.clone();
	for(var i = h.length-1; i > 0; i--){
		if(h[i].hiddenTile){
			h[i] = [hiddenTile].clone()[0];
		}
	}
	return h;
}
/**
 * @see Player#isDoujun
 */
Player.prototype.makeDoujun = function(){
	if(this.isDoujun){
		return true
	}
	if(Game.turn == this.id){
		return false
	}
	if(Score.yakuList(this, players[Game.turn].discardTiles.lastItem())){
		this.isDoujun = true;
		return true
	}
	return false
}
Player.prototype.skipAction = function(){
	
}
//AI
/*
 * The computer's AI.
 * AI is a function with one param variable. The variable means whether it is player's turn
 * @type function
 * @see Player#run
 */
Player.prototype.__defineSetter__("AI", function(AI){
	if(typeof AI != "function"){
		return;
	}
	prototype.__defineGetter__("AI", function(){return AI});
});
Player.prototype.__defineGetter__("AI", function(){return DEFAULT_AI});
/**
 * Running the AI of the player.
 */
Player.prototype.run = function(){
	if(Game.turn == this.id){
		if(Game.isRoundFinish){
			//If somebody win the round or the round is drew, then do not did any action
			return false
		}
		//It is the players' turn
		this.pickUp();
		//Run AI to choose the tile be discard
		this.AI();
	}else{
		//It is not player's turn. AI is used for choose whether chowing, konging, or ponging.
		this.AI();
	}
}
/**
 * A simple ai.
 * @this Player
 * @see Player#run
 * @see Player#AI
 * @see DataTransfer
 */
function DEFAULT_AI(){
	var _this = this;
	if(_this.id == Game.turn){
		// it is the players's turn
		var s = Score.yakuList(_this);
		if(s){
			DataTransfer.sendMessage({
				player: _this,
				action: ActionMessage.SelfDrawn
			});
			/*
			Game.countScore(s, _this, _this.pickingUpTile);
			alert(_this.name + " self-drawn.\n " + s.yakus + "\n" + s.score + "points.");
			Game.finishRound();
			*/
		}else{
			_this.toDiscardTile(_this.pickingUpTile);
		}
	}else{
		var d = players[Game.turn].discardTiles.lastItem();
		var s = Score.yakuList(_this, d);
		if(s){
			DataTransfer.sendMessage(new DataTransfer.data(_this, ActionMessage.Ron));
			//Game.countScore(s, _this, players[Game.turn])
			//alert(_this.name + " win.\n " + s.yakus + "\n" + s.score + "points.");
			//Game.finishRound();
		}else{
			if(_this.canKong(d)){
				DataTransfer.sendMessage(new DataTransfer.data(_this, ActionMessage.Kong));
			}
			if(_this.canPong(d)){
				DataTransfer.sendMessage(new DataTransfer.data(_this, ActionMessage.Pong));
				return true;
			}
			if(_this.canChow(d)){
				var chowlist = _this.chowChoise(d);
				
				DataTransfer.sendMessage(new DataTransfer.data(_this, ActionMessage.ActionMessage.Chow, chowlist));
				return true;
			}
			_this.skipAction();
		}
	}
	
}

/**
 * @namespace
 */
var Score = new function(){
	var automatically_awarded_fu = 20;
	/**
	 * count 符
	 * @param	{HandInfo}	agari_handinfo	The handinfo of Agari hand.
	 * @param	{HandInfo[]}	ready_hand_detail	The details of the hand without agari tile.
	 * @param	{Array[]}	[playermeld=[]]	The meld of the owner of the hand
	 * @param	{Boolean}	self_draw
	 * @param	{Boolean}	isRinshan	whether the agari tile is rinshan tile
	 * @param	{Boolean}	agari_from_wall Whether the agari tile is from wall.
	 * @param	{Number}	playerWind	the wind of the player. It is used for checking the Honor tiles in the pair.
	 */
	function _fu(agari_handinfo, ready_hand_detail, playermeld, self_draw, isRinshan, agari_from_wall, playerWind){
		if(!agari_handinfo || !(ready_hand_detail instanceof Array))
			return;
		//special yaku
		if(agari_handinfo.pairs.length == 7){
			// 7 pairs
			return Special_Fu_Value.Seven_Pairs;
		}
		var f = automatically_awarded_fu;
		if(!(playermeld instanceof Array))
			playermeld = [];
		var is_door_clear = true;
		for(var i = 0; i < playermeld.length; i++){
			if(playermeld[i][0] == "KONG"){
				if(playermeld[i].kongType == KongType.Open_Quad || playermeld[i].kongType == KongType.Added_Open_Quad){
					is_door_clear = false;
					break;
				}
			}else{
				is_door_clear = false;
				break;
			}
		}

		//The fu from meld
		for(i = 0; i < playermeld.length; i++){
			var m = playermeld[i];
			var t = playermeld[i].takedTile;
			switch(m.type){
				case MeldType.PONG:
					if(t.isYaoChuuTile){
						f += 4;
					}else{
						f += 2;
					}
					break;
				case MeldType.KONG:
					if(m.kongType == KongType.Open_Quad || m.kongType == KongType.Added_Open_Quad){
						if(t.typeCode == 3 || t.simpleNum == 1 || t.simpleNum == 9){
							f += 16;
						}else{
							f += 8;
						}
					}else{
						if(t.typeCode == 3 || t.simpleNum == 1 || t.simpleNum == 9){
							f += 32;
						}else{
							f += 16;
						}
					}
					break;
			}
		}


		//the fu from hand
		agari_handinfo.concealedTriplets.forEach(function(val, index, arr){
			if(val[0].isYaoChuuTile){
				f += 8;
			}else{
				f += 4;
			}
		});

		//the fu form pairs
		if(agari_handinfo.pairs[0][0].typeCode == MahjongType.Word){
			switch(agari_handinfo.pairs[0][0].wordNum){
				case MahjongWordNumCode.Haku:
					f += 2;
					break;
				case MahjongWordNumCode.Hatsu:
					f += 2;
					break;
				case MahjongWordNumCode.Chun:
					f += 2;
					break;
				case RoundDetail.code:
					// 場風
					f += 2;
				case playerWind:
					// 自風
					f += 2;
					break;
			}
		}

		//the fu of waiting tiles
		if(ready_hand_detail.length == 1){
			var r = ready_hand_detail[0];
			if(r.unused.length == 1 && r.unused[0] instanceof Array){
				if(r.unused[0][0].simpleNum == 1 || r.unused[0][1].simpleNum == 9){
					//辺張待ち
					f += 2;
				}
			}else{
				//嵌張待ち / 単騎待ち
				f += 2;
			}
		}

		//the fu from self draw (if it is 平和 or 嶺上開花, it won't be counted)
		if(is_door_clear && self_draw && !isRinshan && f != automatically_awarded_fu){
			f += 2;
		}

		if(is_door_clear && !self_draw){
			f += 10;
		}

		f = Math.ceil(f / 10) * 10;
		return f;
	}
	/**
	 * @function
	 * @see Score-normalScorePoint
	 */
	this.scorePoint = function(/** Number */fu,/** Number */ han){
		var result = fu * Math.pow(2,han+2);
		if(Rules.Aotenjou)
			return result;
		else
			return normalScorePoint(result, han);
	}
	/**
	 * @function
	 * @param {Number} point	the score
	 * @param {Number} han	the number of 飜
	 * @see Score.scorePoint
	 */
	function normalScorePoint(point, han){
		if(point < 2000)
			return point;
		switch(han){
			case 1:
			case 2:
			case 3:
			case 4:
			case 5:
				//満貫（まんがん）
				return 2000;
			case 6:
			case 7:
				//跳満（はねまん）
				return 3000;
			case 8:
			case 9:
			case 10:
				//倍満（ばいまん）
				return 4000;
			case 11:
			case 12:
				//三倍満（さんばいまん）
				return 6000;
			default:
				//累計役滿	/	数え役満	/	counted yakuman
				return 8000;
		}
	};
	/**
	 * Checking whether the winning player makes sacred discard（振聴／フリテン）.
	 * @see Score.yakuList
	 * @param {HandInfo[]} readyHandResults
	 * @param {MahjongTile} tile The agari tile.
	 * @param {MahjongTile[]} discardTiles	The discard tiles of the player.
	 * @return {Boolean} If it return true, means the player makes
	 */
	function sacredDiscardChecking(readyHandResults, tile, discardTiles){
		//TODO 目前已經完成普通振聽，未完成其他
		//TODO 為完成振聽，需要能判斷是否見逃
		//TODO 三麻拔北
		if(!(winner instanceof Player) || !(tile instanceof MahjongTile) || !(discardTiles instanceof Array)){
			return false;
		}
		var waitingTiles = [];
		readyHandResults.forEach(function(r){
			if(r.unused.length == 1 && (r.unused[0] instanceof MahjongTile)){
				//一張牌單騎
				waitingTiles.push(r.unused[0]);
			}else if(r.unused.length == 1 && (r.unused[0] instanceof Array)){
				// 順子缺一
				var unusedTiles = r.unused[0];
				if(unusedTiles[1].simpleNum - unusedTiles[0].simpleNum == 1){
					if(unusedTiles[0].simpleNum == 1){
						var m = new MahjongTile;
						m.typeCode = unusedTiles[0].typeCode;
						m.simpleNum = unusedTiles[1].simpleNum + 1;
						waitingTiles.push(m);
					}else if(unusedTiles[1].simpleNum == 9){
						var m = new MahjongTile;
						m.typeCode = unusedTiles[0].typeCode;
						m.simpleNum = unusedTiles[0].simpleNum - 1;
						waitingTiles.push(m);
					}else{
						var m1 = new MahjongTile, m2 = new MahjongTile;
						m1.typeCode = unusedTiles[0].typeCode;
						m2.typeCode = unusedTiles[0].typeCode;
						m1.simpleNum = unusedTiles[1].simpleNum + 1;
						m2.simpleNum = unusedTiles[0].simpleNum - 1;
						waitingTiles.push(m1);
						waitingTiles.push(m2);
					}
				}else if(unusedTiles[1].simpleNum - unusedTiles[0].simpleNum == 2){
					var m = new MahjongTile;
					m.typeCode = unusedTiles[0].typeCode;
					m.simpleNum = unusedTiles[0].simpleNum + 1;
					waitingTiles.push(m);
				}
			}else if(r.pairs.length == 2){
				// 雙碰聽
				waitingTiles.push(r.pairs[0][0])
				waitingTiles.push(r.pairs[1][0])
			}else if(r.unused.length == 11 && r.pairs.length == 1){
				var checkingList = [], i;
				for(i = 0; i < 13; i++){
					var m = new MahjongTile;
					switch(i){
						case 0:
							m.typeCode = MahjongType.Word;
							m.wordNum = MahjongWordNumCode.East;
							break;
						case 1:
							m.typeCode = MahjongType.Word;
							m.wordNum = MahjongWordNumCode.South;
							break;
						case 2:
							m.typeCode = MahjongType.Word;
							m.wordNum = MahjongWordNumCode.West;
							break;
						case 3:
							m.typeCode = MahjongType.Word;
							m.wordNum = MahjongWordNumCode.North;
							break;
						case 4:
							m.typeCode = MahjongType.Word;
							m.wordNum = MahjongWordNumCode.Haku;
							break;
						case 5:
							m.typeCode = MahjongType.Word;
							m.wordNum = MahjongWordNumCode.Hatsu;
							break;
						case 6:
							m.typeCode = MahjongType.Word;
							m.wordNum = MahjongWordNumCode.Chun;
							break;
						case 7:
							m.typeCode = MahjongType.Pin;
							m.simpleNum = 1;
							break;
						case 8:
							m.typeCode = MahjongType.Pin;
							m.simpleNum = 9;
							break;
						case 9:
							m.typeCode = MahjongType.Wan;
							m.simpleNum = 1;
							break;
						case 10:
							m.typeCode = MahjongType.Wan;
							m.simpleNum = 9;
							break;
						case 11:
							m.typeCode = MahjongType.Sou;
							m.simpleNum = 1;
							break;
						case 12:
							m.typeCode = MahjongType.Sou;
							m.simpleNum = 9;
							break;
					}
					m.typeCode = unusedTiles[0].typeCode;
					m.simpleNum = unusedTiles[0].simpleNum + 1;
					checkingList.push(m);
				}
				for(i = 0; i < r.unused.length; i++){
					for(var j = 0; j < checkingList.length; j++){
						if(checkingList[j].isSameTile(r.unused[i])){
							checkingList.removeByIndex(j);
							break;
						}
					}
				}
				for(var i = 0; i < checkingList.length; i++){
					if(checkingList[i].isSameTile(r.pairs[0])){
						checkingList.removeByIndex(i);
						break;
					}
				}
				checkingList.forEach(function(val){
					waitingTiles.push(val);
				});
			}else if(r.unused.length == 13 && r.pairs.length == 0){
				// 国士無双十三面
				r.unused.forEach(function(val){
					waitingTiles.push(val);
				});
			}
		});
		for(var i = 0; i < discardTiles.length; i++){
			for(var j = 0; j < waitingTiles.length; j++){
				if(discardTiles[i].isSameTile(waitingTiles[j])){
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * To count how many yaku (Japaness : 役) the winner get
	 * @param {Player} p The winner
	 * @return {ScoreResult|Boolean} The detail of Yakus. Including the name of yakus, han, numbers of yakumans, score point
	 * @see Hand.countMeldsInHand
	 * @see Score-fu
	 */
	this.yakuList = function(p, lastTile){
		if(!(p instanceof Player)) return false;
		if(!lastTile){
			if(p.pickingUpTile){
				lastTile = p.pickingUpTile;
			}else{
				return false;
			}
		}
		var i;
		//MahjongTiles[]
		var agariHand = p.hand.clone(),		//the finish hand with
		    playerHand = p.hand.clone();	//player's hand

		//hand result
		var playerHandWithAgariTile = p.hand.clone();
		playerHandWithAgariTile.push(lastTile);

		var readyHandResults = Hand.countMeldsInHand(p.hand),
		    agariHandResults = Hand.countMeldsInHand(playerHandWithAgariTile);
		for(i = readyHandResults.length-1; i >= 0; i--){
			//check whether it is ready hand
			if(! Hand.isReadyHand(readyHandResults[i]) ){
				readyHandResults.removeByIndex(i);
			}
		}
		for(i = agariHandResults.length-1; i >= 0; i--){
			//check whether it is ready hand
			if(! Hand.isFinishHand(agariHandResults[i]) ){
				agariHandResults.removeByIndex(i);
			}
		}
		// avoid 国士無双 affect checking result
		readyHandResults.sort(function(a, b){
			return b.unused.length - a.unused.length
		});
		agariHandResults.sort(function(a, b){
			return b.unused.length - a.unused.length
		});
		if(agariHandResults.length > 1){
			var hasNormalHand = false;
			for(i = agariHandResults.length-1; i >= 0; i--){
				if(agariHandResults[i].unused.length == 0){
					hasNormalHand = true;
					continue;
				}
				if(hasNormalHand && agariHandResults[i].unused.length > 1){
					agariHandResults.removeByIndex(i);
				}
			}
			if(hasNormalHand && readyHandResults.length > 1){
				for(i = readyHandResults.length-1; i >= 0; i--){
					if(readyHandResults[i].unused.length > 1){
						readyHandResults.removeByIndex(i);
					}
				}
			}
		}


		if(readyHandResults.length == 0 || agariHandResults.length == 0){
			return false;
		}


		//result details
		var yakuList = [],	// the list of the yakus
		    han = 0,		// the number of 飜 player get
		    yakumanList = [],
		    yakumans = 0;	// the number of yakumans


		agariHand.push(lastTile);
		var doorClear = p.isDoorClear;
		var meld_detail = {PONG: 0, KONG: 0, CHOW: 0}
		//To set agariHand as an 和牌
		for(i = 0; i < p.meld.length; i++){
			var m = p.meld[i]
			if(m.type == MeldType.PONG){
				meld_detail.PONG++;
				m.tiles.forEach(function(val,index,arr){
					agariHand.push(val);
				});
			}else if(m.type == MeldType.KONG){
				meld_detail.KONG++;
				agariHand.push(m.tiles[0],m.tiles[1],m.tiles[2]);
			}else if(m.type == MeldType.CHOW){
				meld_detail.CHOW++
				m.tiles.forEach(function(val,index,arr){
					agariHand.push(val);
				});
			}
		}
		Hand.sort(agariHand);
		var check_color = {
			wan: 0,		//萬子
			banboo: 0,	//索
			pin: 0,		//餅
			green: 0,	//綠色牌
			terminals: 0,	//老頭牌
			honors: 0	//字牌
		}

		for(i = 0; i < agariHand.length; i++){
			var m = agariHand[i];
			switch(m.typeCode){
				case MahjongType.Pin:
					check_color.pin += 1;
					break;
				case MahjongType.Wan:
					check_color.wan += 1;
					break;
				case MahjongType.Sou:
					check_color.banboo += 1;
					break;
				case MahjongType.Word:
					check_color.honors += 1;
					break;
			}

			if(m.isGreen){
				check_color.green++;
			}

			if(m.simpleNum == 1 || m.simpleNum == 9){
				check_color.terminals ++;
			}
		}
		//僅有一種數牌
		var only_one_type_of_number_tile = (
			((Boolean(check_color.wan) || Boolean(check_color.banboo) || Boolean(check_color. pin)) == true) &&
			((Boolean(check_color.wan) && Boolean(check_color.banboo) && Boolean(check_color. pin)) == false) &&
			((Boolean(check_color.wan) ^  Boolean(check_color.banboo) ^  Boolean(check_color. pin)) == true)
		)

		/*
		 * 役満
		 * *********************************/
		//天和	テンホウ		Heavenly hand
		if(doorClear && RoundDetail.pickedUpTilesNum == 1){
			yakumanList.push("天和");
			yakumans++;
		}
		//地和	チーホウ		Hand of earth
		if(doorClear &&
		   RoundDetail.pickedUpTilesNum != 1 && RoundDetail.pickedUpTilesNum <= players.length &&
		   (RoundDetail.pickedUpTilesNum - RoundDetail.dealer  + 1 + 4) % 4 == p.id &&
		   lastTile == p.pickingUpTile){
			var canHandOfEarth = true;
			for(i = p.index; i >= 0; i--){
				if(players[i].meld.length != 0){
					canHandOfEarth = false;
				}
			}
			if(canHandOfEarth){
				yakumanList.push("地和");
				yakumans++;
			}
		}
		//四槓子	スーカンツ		Four kans
		if(meld_detail.KONG == 4){
			yakumanList.push("四槓子");
			yakumans++;
		}
		//清老頭	チンロートー		All terminals
		if(check_color.terminals == agariHand.length){
			yakumanList.push("清老頭");
			yakumans++;
		}
		//緑一色	リューイーソー		All green
		if(check_color.green == agariHand.length){
			yakumanList.push("緑一色");
		}
		//字一色	ツーイーソー		All honors
		if(check_color.honors == agariHand.length){
			yakumanList.push("字一色");
			yakumans++;

		}

		//九蓮宝燈	チューレンポウトー	Nine gates
		if(doorClear && p.meld.length == 0 && only_one_type_of_number_tile){
			var agari_result = {"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0};
			playerHandWithAgariTile.forEach(function(val, index, arr){
				agari_result[val.simpleNum]++;
			});
			if(agari_result[1] >= 3 &&
			   agari_result[2] >= 1 &&
			   agari_result[3] >= 1 &&
			   agari_result[4] >= 1 &&
			   agari_result[5] >= 1 &&
			   agari_result[6] >= 1 &&
			   agari_result[7] >= 1 &&
			   agari_result[8] >= 1 &&
			   agari_result[9] >= 3){
				yakumanList.push("九蓮宝燈");
				yakumans++;
			}
		}
		/*
		 * ダブル役満
		 * *********************************/
		if(Rules.Double_Yakuman){
			//純正九蓮宝燈	九蓮寶燈九面待ち
			if(yakumanList.indexOf("九蓮宝燈") >= 0){
				var hand_result = {"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0};
				playerHand.forEach(function(val, index, arr){
					hand_result[val.simpleNum]++;
				});
				if(hand_result[1] == 3 &&
				   hand_result[2] == 1 &&
				   hand_result[3] == 1 &&
				   hand_result[4] == 1 &&
				   hand_result[5] == 1 &&
				   hand_result[6] == 1 &&
				   hand_result[7] == 1 &&
				   hand_result[8] == 1 &&
				   hand_result[9] == 3){
					yakumanList.remove("九蓮宝燈");
					yakumanList.push("純正九蓮宝燈");
					yakumans++;

				}

			}
		}

		/*
		 * 根據牌來決定的役
		 * *********************************/
		/* 六飜 */
		//清一色	チンイーソー	Flush
		if(check_color.honors == 0 && only_one_type_of_number_tile){
			yakuList.push("清一色");
			han += 6
			if(!doorClear)
				han -= 1;	//非門前減飜
		}
		/* 三飜 */
		//混一色	ホンイーソー	Half-flush
		if(check_color.honors != 0 &&only_one_type_of_number_tile){
			yakuList.push("混一色");
			han += 3
			if(!doorClear)
				han -= 1;	//非門前減飜
		}
		/* 一飜 */
		//断么九	タンヤオチュー	All simples
		if(check_color.terminals + check_color.honors == 0){
			yakuList.push("断么九");
			han += 1
		}
		/*
		 * 根據時間決定的役
		 * *********************************/
		/* 二飜 */
		//ダブル立直	ダブルリーチ		Double-ready
		if(p.isDoubleRichi && doorClear){
			yakuList.push("Ｗ立直");
			han += 2;
		}
		/* 一飜 */
		//立直		リーチ			Ready hand
		if(p.isRichi && !p.isDoubleRichi && doorClear){
			yakuList.push("立直");
			han += 1;
		}
		//一発		イッパツ			One-shot
		if(p.canOneShotTurn){
			yakuList.push("一發");
			han += 1;
		}
		//門前清自摸和	メンゼンチンツモホウ	Self-pick
		if(lastTile == p.pickingUpTile && doorClear){
			yakuList.push("自摸");
			han += 1;
		}
		//嶺上開花		リンシャンカイホウ		Dead wall draw
		if(lastTile == p.pickingUpTile && lastTile.isRinshanTile){
			yakuList.push("嶺上開花");
			han += 1;
		}
		//海底摸月／海底撈月		ハイテイモーユエ／ハイテイラオユエ		Last tile from the wall
		if(lastTile == p.pickingUpTile && lastTile.pickingTileNum == bgColor="transparent"){
			yakuList.push("海底撈月");
			han += 1;
		}
		//河底撈魚		ホーテイラオユイ		Last discard
		if(lastTile != p.pickingUpTile && lastTile.pickingTileNum == bgColor="transparent"){
			yakuList.push("河底撈魚");
			han += 1;
		}
		/*
		 * 根據牌型決定的役
		 * *********************************/
		var yakuresult_list = [];
		agariHandResults.forEach(function(val, index, arr){
			var fu = _fu(val, readyHandResults, p.meld, lastTile == p.pickingUpTile, lastTile.isRinshanTile, p.wind);
			var _yakumanList = yakumanList.clone(),
			    _yakuList = yakuList.clone();
			var _yakumans = yakumans,
			    _han = han;
			/*
			 * 役満
			 * *********************************/
			//四暗刻	スーアンコー		Four concealed triplets
			var num_of_concealed_triplets = 0;
			val.concealedTriplets.forEach(function(_val, _index, _arr){
				var allFromHand = true
				_val.forEach(function(__val, __index, __arr){
					if(__val.owner.id != p.id){
						allFromHand = false;
					}
				});
				if(allFromHand){
					num_of_concealed_triplets++;
				}
			});
			p.meld.forEach(function(_val, _index, _arr){
				if(_val.type == MeldType.KONG &&
				   (_val.kongType == KongType.Closed_Quad_from_Hand || _val.kongType == KongType.Closed_Quad_from_PickupTile)){
					num_of_concealed_triplets++;
				}
			});
			if(num_of_concealed_triplets == 4){
				_yakumanList.push("四暗刻");
				_yakumans++;
			}
			//小四喜	ショウスーシー		Little four winds
			var check_word_in_triplets = {"east":0,"west":0,"south":0,"north":0,"haku":0,"hatsu":0,"chun":0},
			    check_word_in_pair = {"east":0,"west":0,"south":0,"north":0,"haku":0,"hatsu":0,"chun":0};
			val.concealedTriplets.forEach(function(_val, _index, _arr){
				if(_val[0].typeCode == MahjongType.Word){
					switch(_val[0].wordNum){
						case MahjongWordNumCode.East:
							check_word_in_triplets.east++;
							break;
						case MahjongWordNumCode.South:
							check_word_in_triplets.west++;
							break;
						case MahjongWordNumCode.West:
							check_word_in_triplets.south++;
							break;
						case MahjongWordNumCode.North:
							check_word_in_triplets.north++;
							break;
						case MahjongWordNumCode.Haku:
							check_word_in_triplets.haku++;
							break;
						case MahjongWordNumCode.Hatsu:
							check_word_in_triplets.hatsu++;
							break;
						case MahjongWordNumCode.Chun:
							check_word_in_triplets.chun++;
							break;
					}
				}
			});
			p.meld.forEach(function(_val, _index, _arr){
				if(_val.type != MeldType.KONG && _val.type != MeldType.PONG){
					return;
				}
				if(_val.takedTile.typeCode == MahjongType.Word){
					switch(_val.takedTile.wordNum){
						case MahjongWordNumCode.East:
							check_word_in_triplets.east++;
							break;
						case MahjongWordNumCode.South:
							check_word_in_triplets.west++;
							break;
						case MahjongWordNumCode.West:
							check_word_in_triplets.south++;
							break;
						case MahjongWordNumCode.North:
							check_word_in_triplets.north++;
							break;
						case MahjongWordNumCode.Haku:
							check_word_in_triplets.haku++;
							break;
						case MahjongWordNumCode.Hatsu:
							check_word_in_triplets.hatsu++;
							break;
						case MahjongWordNumCode.Chun:
							check_word_in_triplets.chun++;
							break;
					}
				}
			});
			if(val.pairs[0][0].typeCode == MahjongType.Word){
				switch(val.pairs[0][0].wordNum){
					case MahjongWordNumCode.East:
						check_word_in_pair.east++;
						break;
					case MahjongWordNumCode.South:
						check_word_in_pair.west++;
						break;
					case MahjongWordNumCode.West:
						check_word_in_pair.south++;
						break;
					case MahjongWordNumCode.North:
						check_word_in_pair.north++;
						break;
					case MahjongWordNumCode.Haku:
						check_word_in_pair.haku++;
						break;
					case MahjongWordNumCode.Hatsu:
						check_word_in_pair.hatsu++;
						break;
					case MahjongWordNumCode.Chun:
						check_word_in_pair.chun++;
						break;
				}
			}

			if(check_word_in_triplets.east + check_word_in_triplets.south + check_word_in_triplets.west + check_word_in_triplets.north >= 3 &&
			   check_word_in_triplets.east + check_word_in_triplets.south + check_word_in_triplets.west + check_word_in_triplets.north + check_word_in_pair.east + check_word_in_pair.south + check_word_in_pair.west + check_word_in_pair.north == 4 ){
				_yakumanList.push("小四喜");
				_yakumans++;
			}

			//大三元	ダイサンゲン		Big three dragons
			if(check_word_in_triplets.haku && check_word_in_triplets.hatsu && check_word_in_triplets.chun){
				_yakumanList.push("大三元");
				_yakumans++;
			}
			//国士無双	コクシムソウ		Thirteen orphans
			if(doorClear && val.unused.length == 12 && val.pairs){
				_yakumanList.push("国士無双");
				_yakumans++;
			}
			/*
			 * W役満
			 * *********************************/
			if(Rules.Double_Yakuman){
				//四暗刻単騎	スーアンコーたんき	Four concealed triplets single wait
				if(_yakumanList.indexOf("四暗刻") >= 0){
					var isSingleWait = false;
					readyHandResults.forEach(function(_val, _index, _arr){
						if(_val.unused.length == 1 && _val.pairs.length == 0 && _val.straight.length == 0){
							isSingleWait = true;
						}
					});
					if(isSingleWait){
						_yakumanList.remove("四暗刻");
						_yakumanList.push("四暗刻単騎");
						_yakumans++;
					}
				}
				//大四喜		ダイスーシー	Big four winds
				if(_yakumanList.indexOf("小四喜") >= 0){
					if(check_word_in_triplets.east + check_word_in_triplets.south + check_word_in_triplets.west + check_word_in_triplets.north == 4){
						_yakumanList.remove("小四喜");
						_yakumanList.push("大四喜");
						_yakumans++;
					}
				}
				//国士無双十三面待ち	國士無雙十三面聽	Thirteen orphans 13 wait
				if(_yakumanList.indexOf("国士無双") >= 0){
					var isRisingSun = false;
					for(i = 0; i < readyHandResults.length; i++){
						var readyHandResult = readyHandResults[i];
						if(isRisingSun){
							break;
						}
						if(readyHandResult.concealedTriplets.length >0 ||
						   readyHandResult.straight.length > 0 ||
						   readyHandResult.pairs.length > 0){
							isRisingSun = false;
							break
						}
						if(readyHandResult.unused.length == 13){
							isRisingSun = true;
						}
					}

					if(isRisingSun){
						_yakumanList.remove("国士無双");
						_yakumanList.push("国士無双十三面待ち");
						_yakumans++;
					}
				}
			}
			/*
			 * 普通役
			 * *********************************/
			if(Rules.Aotenjou || _yakumanList.length == 0 || _yakumans == 0){
				/* 三飜 */
				//二盃口	リャンペーコー	Two sets of identical sequences
				var num_of_sets = 0;
				val.straight.sort(function(a, b){
					Hand.sort(a);
					Hand.sort(b);
					if(a[0].typeCode != b[0].typeCode){
						return a[0].typeCode - b[0].typeCode;
					}else if(a[0].simpleNum != b[0].simpleNum){
						return a[0].simpleNum - b[0].simpleNum;
					}
				});
				for(i = 0; i < val.straight.length - 1; ){
					if(val.straight[i][0].isSameTile(val.straight[i+1][0])){
						num_of_sets++;
						i+=2;
					}else{
						i++;
					}
				}
				if(doorClear && num_of_sets == 2){
					_yakuList.push("二盃口");
					_han += 3;
				}
				/* 二飜 */
				//七対子	チートイツ	7 pairs
				if(doorClear && val.pairs.length == 7){
					_yakuList.push("七対子");
					_han += 2;
				}
				//対々和	トイトイホー		All triplet hand
				if(meld_detail.CHOW == 0 && val.straight.length == 0 && val.concealedTriplets.length + p.meld.length == 4){
					_yakuList.push("対々和");
					_han += 2;
				}
				//三暗刻	サンアンコー		Three closed triplets
				if(num_of_concealed_triplets == 3){
					_yakuList.push("三暗刻");
					_han += 2;
				}
				//三槓子	サンカンツ		Three kans
				if(meld_detail.KONG == 4){
					_yakuList.push("三槓子");
					_han += 2;
				}
				/* 一飜 */
				//平和	ピンフ	No-points hand
				if(doorClear &&
				   p.meld.length == 0 &&
				   val.straight.length == 4 &&
				   fu == 20 + (lastTile == p.pickingUpTile? 0: 10)){
					var whether_no_points_hand = false;
					val.straight.forEach(function(_val, _arr){
						if(whether_no_points_hand)
							return;
						Hand.sort(_val);
						if(_val.indexOf(lastTile) >= 0 &&
						   (_val.indexOf(lastTile) == 0 || _val.indexOf(lastTile) == _val.length - 1)){
							if(!(_val[0].simpleNum == 1 && _val.indexOf(lastTile) == _val.length - 1) &&
							   !(_val[0].simpleNum == 7 && _val.indexOf(lastTile) == 0)){
								whether_no_points_hand = true;
							}
						}
					});
					if(whether_no_points_hand){
						_yakuList.push("平和");
						_han += 1;
					}
				}
				//一盃口	イーペーコー	One set of identical sequences
				if(doorClear && num_of_sets == 1){
					_yakuList.push("一盃口");
					_han += 1;
				}
				/*
				 * 混合
				 * *********************************/
				/* 三飜 */
				//純全帯么九	ジュンチャンタイヤオチュー	Terminal in each set
				var num_of_sets_which_have_terminal_tile = 0,
				    num_of_sets_which_have_honor_tile = 0;
				p.meld.forEach(function(_val, _index, _arr){
					switch(_val.type){
						case MeldType.KONG:
						case MeldType.PONG:
							if(_val.takedTile.isYaoChuuTile){
								if(_val.takedTile.typeCode == MahjongType.Word){
									num_of_sets_which_have_honor_tile++;
								}else{
									num_of_sets_which_have_terminal_tile++;
								}
							}
							break;
						case MeldType.CHOW:
							_val.takedTile.tiles.forEach(function(__val, __index, __arr){
								if(__val.isYaoChuuTile){
									num_of_sets_which_have_terminal_tile++;
								}
							});
							break;
						default:
							break;
					}
				});
				val.pairs.forEach(function(_val, _index, _arr){
					if(_val[0].isYaoChuuTile){
						if(_val[0].typeCode == MahjongType.Word){
							num_of_sets_which_have_honor_tile++;
						}else{
							num_of_sets_which_have_terminal_tile++;
						}
					}
				});
				val.concealedTriplets.forEach(function(_val, _index, _arr){
					if(_val[0].isYaoChuuTile){
						if(_val[0].typeCode == MahjongType.Word){
							num_of_sets_which_have_honor_tile++;
						}else{
							num_of_sets_which_have_terminal_tile++;
						}
					}
				});
				val.straight.forEach(function(_val, _index, _arr){
					_val.forEach(function(__val, __index, __arr){
						if(__val.isYaoChuuTile){
							num_of_sets_which_have_terminal_tile++;
						}
					});
				});
				if(num_of_sets_which_have_terminal_tile == 5 &&
				   num_of_sets_which_have_honor_tile == 0 &&
				   val.straight.length + meld_detail.CHOW > 0){
					_yakuList.push("純全帯么九");
					_han += 3;
					if(!doorClear)
						han -= 1;	//非門前減飜
				}
				/* 二飜 */
				//混老頭		ホンロウトー	All terminals and honors
				if(val.unused.length == 0 &&	//avoid 国士無双
				   check_color.terminals + check_color.honors  == agariHand.length &&
				   check_color.terminals != 0 && check_color.honors != 0){
					_yakuList.push("混老頭");
					_han += 2;
				}
				//混全帯么九	ホンチャンタイヤオチュー	Terminal or honor in each set
				if(num_of_sets_which_have_terminal_tile + num_of_sets_which_have_honor_tile == 5 &&
				   num_of_sets_which_have_terminal_tile != 0 &&
				   num_of_sets_which_have_honor_tile != 0 &&
				   val.straight.length + meld_detail.CHOW > 0){
					_yakuList.push("混全帯么九");
					_han += 2;
					if(!doorClear)
						han -= 1;	//非門前減飜
				}
				//小三元		ショウサンゲン	Little three dragons
				if(check_word_in_triplets.haku + check_word_in_triplets.hatsu + check_word_in_triplets.chun == 2 &&
				   check_word_in_pair.haku + check_word_in_pair.hatsu + check_word_in_pair.chun == 1){
					_yakuList.push("小三元");
					_han += 2;
				}
				//三色同順	サンショクドウジュン	Three colour straight
				var straight_detail = {}
				val.straight.forEach(function(_val, _index, _arr){
					Hand.sort(_val);
					if(!straight_detail[_val[0].simpleNum]){
						straight_detail[_val[0].simpleNum] = {};
					}
					switch(_val[0].typeCode){
						case MahjongType.Pin:
							straight_detail[_val[0].simpleNum].Pin = true;
							break;
						case MahjongType.Wan:
							straight_detail[_val[0].simpleNum].Wan = true;
							break;
						case MahjongType.Sou:
							straight_detail[_val[0].simpleNum].Sou = true;
							break;
					}
				});
				p.meld.forEach(function(_val, _index, _arr){
					if(_val.type != MeldType.CHOW)
						return;
					if(!straight_detail[_val.tiles[0].simpleNum]){
						straight_detail[_val.tiles[0].simpleNum] = {};
					}
					switch(_val.tiles[0].typeCode){
						case MahjongType.Pin:
							straight_detail[_val.tiles[0].simpleNum].Pin = true;
							break;
						case MahjongType.Wan:
							straight_detail[_val.tiles[0].simpleNum].Wan = true;
							break;
						case MahjongType.Sou:
							straight_detail[_val.tiles[0].simpleNum].Sou = true;
							break;
					}
				});
				for(i in straight_detail){
					if(straight_detail[i].Pin && straight_detail[i].Wan && straight_detail[i].Sou){
						_yakuList.push("三色同順");
						_han += 2;
						if(!doorClear)
							han -= 1;	//非門前減飜
						break;
					}
				}
				//一気通貫	イッキツウカン		Straight
				if(straight_detail[1] && straight_detail[4] && straight_detail[7] &&
				   ((straight_detail[1].Pin && straight_detail[4].Pin && straight_detail[7].Pin) ||
				    (straight_detail[1].Pin && straight_detail[4].Pin && straight_detail[7].Pin) ||
				    (straight_detail[1].Pin && straight_detail[4].Pin && straight_detail[7].Pin))
				  ){
					_yakuList.push("一気通貫");
					_han += 2;
					if(!doorClear)
						han -= 1;	//非門前減飜
				}
				//三色同刻	サンショクドーコー		Three colour triplets
				var triplets_detail = {}
				val.concealedTriplets.forEach(function(_val, _index, _arr){
					if(!triplets_detail[_val[0].simpleNum]){
						triplets_detail[_val[0].simpleNum] = {};
					}
					switch(_val[0].typeCode){
						case MahjongType.Pin:
							triplets_detail[_val[0].simpleNum].Pin = true;
							break;
						case MahjongType.Wan:
							triplets_detail[_val[0].simpleNum].Wan = true;
							break;
						case MahjongType.Sou:
							triplets_detail[_val[0].simpleNum].Sou = true;
							break;
					}
				});
				p.meld.forEach(function(_val, _index, _arr){
					if(_val.type == MeldType.CHOW || _val.takedTile.typeCode == MahjongType.Word)
						return;
					if(!triplets_detail[_val.takedTile.simpleNum]){
						triplets_detail[_val.takedTile.simpleNum] = {};
					}
					switch(_val.takedTile.typeCode){
						case MahjongType.Pin:
							triplets_detail[_val.takedTile.simpleNum].Pin = true;
							break;
						case MahjongType.Wan:
							triplets_detail[_val.takedTile.simpleNum].Wan = true;
							break;
						case MahjongType.Sou:
							triplets_detail[_val.takedTile.simpleNum].Sou = true;
							break;
					}
				});
				for(i in triplets_detail){
					if(triplets_detail[i].Pin && triplets_detail[i].Wan && triplets_detail[i].Sou){
						_yakuList.push("三色同刻");
						_han += 2;
					}
				}
				/* 一飜 */
				//槍槓		チャンカン		Robbing a quad
				if(lastTile != p.pickingUpTile){
					var canRobbingAQuad
					players[lastTile.owner.id].meld.forEach(function(val, index, arr){
						if(val.type == MeldType.KONG && val.takedTile.isSameTile(lastTile)){
							if(val.kongType == KongType.Added_Open_Quad){
								canRobbingAQuad = true;
							}else if(doorClear && val.unused.length == 12 && val.pairs){
								// if it is 國士無雙， even if it is a closed Quad, it still can be robbed
								canRobbingAQuad = true;
							}
						}
					});
					if(canRobbingAQuad){
						_yakuList.push("槍槓");
						_han += 1;
					}
				}
				//役牌・飜牌・翻牌	ヤクハイ、ファンパイ、ハンパイ	Honor tiles
				if(check_word_in_triplets.haku){
					_yakuList.push("役牌・白");
					_han += check_word_in_triplets.haku;
				}
				if(check_word_in_triplets.hatsu){
					_yakuList.push("役牌・発");
					_han += check_word_in_triplets.hatsu;
				}
				if(check_word_in_triplets.chun){
					_yakuList.push("役牌・中");
					_han += check_word_in_triplets.chun;
				}
				if(check_word_in_triplets.east){
					if(RoundDetail.code == MahjongWordNumCode.East){
						_yakuList.push("場風・東");
						_han += check_word_in_triplets.east;
					}
					if(p.wind == MahjongWordNumCode.East){
						_yakuList.push("自風・東");
						_han += check_word_in_triplets.east;
					}
				}
				if(check_word_in_triplets.south){
					if(RoundDetail.code == MahjongWordNumCode.South){
						_yakuList.push("場風・南");
						_han += check_word_in_triplets.south;
					}
					if(p.wind == MahjongWordNumCode.South){
						_yakuList.push("自風・南");
						_han += check_word_in_triplets.south;
					}
				}
				if(check_word_in_triplets.west){
					if(RoundDetail.code == MahjongWordNumCode.West){
						_yakuList.push("場風・西");
						_han += check_word_in_triplets.west;
					}
					if(p.wind == MahjongWordNumCode.South){
						_yakuList.push("自風・西");
						_han += check_word_in_triplets.west;
					}
				}
				if(check_word_in_triplets.north){
					if(RoundDetail.code == MahjongWordNumCode.North){
						_yakuList.push("場風・北");
						_han += check_word_in_triplets.north;
					}
					if(p.wind == MahjongWordNumCode.South){
						_yakuList.push("自風・北");
						_han += check_word_in_triplets.north;
					}
				}
			}
			var score;
			if(Rules.Aotenjou){
				score = Score.scorePoint(fu, _han + _yakumans * 13 );
			}else if(_yakumans > 0){
				score = _yakumans * 8000;
			}else{
				score = Score.scorePoint(fu, _han);
			}

			yakuresult_list.push({
				yakumanList: _yakumanList,
				num_of_yakumans: _yakumans,
				yakuList: _yakuList,
				han: _han,
				fu: fu,
				score: score,
				handinfo: val
			});
		});

		yakuresult_list.sort(function(a,b){
			return b.score - a.score;
		});
		yakumanList = yakuresult_list[0].yakumanList;
		yakumans = yakuresult_list[0].num_of_yakumans;
		yakuList = yakuresult_list[0].yakuList;
		han = yakuresult_list[0].han;
		var fu = yakuresult_list[0].fu;

		/*
		 * 懸賞牌・ドラ
		 * *********************************/
		if(han || (Rules.Aotenjou && (yakumans || han))){
			var num_of_doras = 0,
			    num_of_RedDoras = 0;

			var doras = Dora.getDoras(Dora.doraIndicators);
			var uraDoras = [];
			if(doorClear){
				uraDoras = Dora.getDoras(Dora.uraDoraIndicators);
			}
			var checkIsDora = function(tile){
				//表ドラ／槓ドラ	Dora
				doras.forEach(function(_val, _index, _arr){
					if(_val.isSameTile(tile)){
						num_of_doras++;
					}
				});
				//裏ドラ／槓ウラ
				if(doorClear){
					uraDoras.forEach(function(_val, _index, _arr){
						if(_val.isSameTile(tile)){
							num_of_doras++;
						}
					});
				}
				//赤ドラ
				if(tile.isRedDora){
					num_of_RedDoras++;
				}
			}

			p.hand.forEach(function(val, index, arr){
				checkIsDora(val);
			});
			checkIsDora(lastTile);
			p.meld.forEach(function(val, index, arr){
				checkIsDora(val.takedTile);
				val.meldTiles.forEach(function(_val, _index, _arr){
					checkIsDora(_val);
				});

				if(val.addKongTile){
					checkIsDora(val.addKongTile);
				}
			});

			if(num_of_doras > 0){
				yakuList.push("ドラ\t"+num_of_doras);
				han += num_of_doras;
			}
			if(num_of_RedDoras > 0){
				yakuList.push("赤ドラ\t"+num_of_RedDoras);
				han += num_of_RedDoras;
			}
		}
		return ScoreResult(yakumanList, yakumans, yakuList, han, fu)
	}
}
/**
 * @class
 * @param {String[]}	yakumans
 * @param {Number}	numOfYakumans
 * @param {String[]}	yakus
 * @param {Number}	han
 * @param {Number}	fu
 */
function ScoreResult(yakumans, numOfYakumans, yakus, han, fu){
	if(!(this instanceof ScoreResult)){
		return new ScoreResult(yakumans, numOfYakumans, yakus, han, fu);
	}
	if(!numOfYakumans && !han){
		this.yakus = yakus || [];
		return false;
	}
	if(Rules.Aotenjou){
		var _yakus = yakumans.clone();
		yakus.forEach(function(val, index, arr){
			_yakus.push(val);
		});

		this.yakus = _yakus;
		this.numOfYakumans = numOfYakumans;
		this.han = numOfYakumans * 13 + han;
		this.fu = fu;
		this.score = Score.scorePoint(fu, numOfYakumans * 13 + han);
	}else{
		if(yakumans.length > 0 && numOfYakumans > 0){
			this.yakus = yakumans;
			this.numOfYakumans = numOfYakumans;
			this.han = numOfYakumans * 13 + han;
			this.fu = fu;
			this.score = numOfYakumans * 8000;
		}else{
			this.yakus = yakus;
			this.numOfYakumans = numOfYakumans;
			this.han = han;
			this.fu = fu;
			this.score = Score.scorePoint(fu, han);
		}
	}
}
/**
 * The list of Yakus. If the rule support Aotenjo, it include Yakemans and normal Yakus.
 * @type String[]
 * @default null
 */
ScoreResult.prototype.yakus = null;
/**
 * The multiple of Yakumans.
 * @default 0
 * @type Number
 */
ScoreResult.prototype.numOfYakumans = 0;
/**
 * @see Score.scorePoint
 * @default 0
 * @type Number
 */
ScoreResult.prototype.score = 0;
/**
 * 符
 * @see fu
 * @see ScoreResult#han
 * @type Number
 */
ScoreResult.prototype.fu = 0;
/**
 * 飜
 * @see ScoreResult#fu
 * @type Number
 */
ScoreResult.prototype.han = 0;

function _Player(/*Player*/p){
	this.__defineGetter__("discardTiles", function(){return p.discardTiles.clone()});
	this.__defineGetter__("hand", function(){return p.hand.clone()});
	this.__defineGetter__("meld", function(){return p.meld.clone()});
	this.__defineGetter__("name", function(){return p.name});
	this.__defineGetter__("score", function(){return p.score});
	this.__defineGetter__("isRichi", function(){return p.isRichi});
	this.__defineGetter__("isDoubleRichi", function(){return p.isDoubleRichi});
	this.__defineGetter__("pickingUpTile", function(){return [p.pickingUpTile].clone()[0]});
}

/**
 * @class
 * @param	{Number}	num_of_players
 * @param	{Array} 	player_names
 * @param	{Array} 	AIs
 * @param	{Object}	rules
 */
	function Mahjong(num_of_players, player_names, AIs, rules){
		if(!(this instanceof Mahjong))
			return new Mahjong(num_of_players, player_names, AIs, rules);
		if(num_of_players!= 3){
			num_of_players = 4;
		}
		if(!(player_names instanceof Array)){
			player_names = [];
		}
		if(!(AIs instanceof Array)){
			AIs = [];
		}

		players = [];
		this.players = [];
		for(var i = 0; i < num_of_players; i++){
			players.push(new Player(player_names[i]));
			players[i].AI  = AIs[i];
			players[i].score = Math.ceil((100000 / num_of_players) / 5000) * 5000;
			this.players[i] = new _Player(players[i])
		}

		if(typeof rules == "object"){
			for(var i in Rules){
				if(rules[i]){
					Rules[i] = rules[i];
				}
			}
		}

	}
	Mahjong.prototype.startNewGame	= Game.startNewGame;
	Mahjong.prototype.countScore	= Game.countScore;
	Mahjong.prototype.drawRound	= Game.drawRound;
	Mahjong.prototype.finishRound	= Game.finishRound;
	Mahjong.prototype.dora  	= Dora;
	Mahjong.prototype.roundDetail	= RoundDetail
	Mahjong.prototype.players = [];
	Mahjong.prototype.__defineSetter__("actionCallback", function(callback){
		if(typeof callback == "function"){
			ActionMessage.prototype.callback = callback;
		}
	});
	Mahjong.prototype.__defineGetter__("hiddenTile", function(callback){
		return hiddenTile
	});
	window.Washizu = Mahjong;
	window.washizu = window.Washizu;
//})();